diff --git a/IDE.properties.tmp b/IDE.properties.tmp index 569427acbc..95fbfd1ac7 100644 --- a/IDE.properties.tmp +++ b/IDE.properties.tmp @@ -27,6 +27,7 @@ rewrite-java-25 # Other language modules +rewrite-docker rewrite-gradle rewrite-groovy rewrite-hcl diff --git a/rewrite-docker/build.gradle.kts b/rewrite-docker/build.gradle.kts new file mode 100644 index 0000000000..aa8b574f6a --- /dev/null +++ b/rewrite-docker/build.gradle.kts @@ -0,0 +1,35 @@ +plugins { + id("org.openrewrite.build.language-library") +} + +val antlrGeneration by configurations.creating { + extendsFrom(configurations.implementation.get()) +} + +tasks.register("generateAntlrSources") { + mainClass.set("org.antlr.v4.Tool") + + args = listOf( + "-o", "src/main/java/org/openrewrite/docker/internal/grammar", + "-package", "org.openrewrite.docker.internal.grammar", + "-visitor" + ) + fileTree("src/main/antlr").matching { include("**/*.g4") }.map { it.path } + + classpath = antlrGeneration + + finalizedBy("licenseFormat") +} + +dependencies { + implementation(project(":rewrite-core")) + implementation("org.antlr:antlr4-runtime:4.13.2") + implementation("io.micrometer:micrometer-core:1.9.+") + + antlrGeneration("org.antlr:antlr4:4.13.2"){ + exclude(group = "com.ibm.icu", module = "icu4j") + } + + compileOnly(project(":rewrite-test")) + + testImplementation(project(":rewrite-test")) +} diff --git a/rewrite-docker/src/main/antlr/DockerLexer.g4 b/rewrite-docker/src/main/antlr/DockerLexer.g4 new file mode 100644 index 0000000000..92c23330af --- /dev/null +++ b/rewrite-docker/src/main/antlr/DockerLexer.g4 @@ -0,0 +1,218 @@ +// $antlr-format alignTrailingComments true, columnLimit 150, maxEmptyLinesToKeep 1, reflowComments false, useTab false +// $antlr-format allowShortRulesOnASingleLine true, allowShortBlocksOnASingleLine true, minEmptyLines 0, alignSemicolons ownLine +// $antlr-format alignColons trailing, singleLineOverrulesHangingColon true, alignLexerCommands true, alignLabels true, alignTrailers true + +lexer grammar DockerLexer; + +@lexer::header +{import java.util.LinkedList; +import java.util.Queue;} + +@lexer::members +{ + // Use a queue (FIFO) for heredoc markers so they are matched in order of declaration + private Queue heredocIdentifiers = new LinkedList(); + private boolean heredocIdentifierCaptured = false; + // Track if we're at the start of a logical line (where instructions can appear) + private boolean atLineStart = true; + // Track if we're after FROM to recognize AS as a keyword (for stage aliasing) + private boolean afterFrom = false; + // Track if we're after HEALTHCHECK to recognize CMD/NONE as keywords + private boolean afterHealthcheck = false; +} + +options { + caseInsensitive = true; +} + +// Parser directives (must be at the beginning of file) +// After a parser directive, we're at line start (it consumes the newline) +PARSER_DIRECTIVE : '#' WS_CHAR* [A-Z_]+ WS_CHAR* '=' WS_CHAR* ~[\r\n]* NEWLINE_CHAR { atLineStart = true; }; + +// Comments (after parser directives) - HIDDEN in main mode +COMMENT : '#' ~[\r\n]* -> channel(HIDDEN); + +// Instructions (case-insensitive) +// Instructions are only recognized at line start. Otherwise they become UNQUOTED_TEXT. +// This eliminates ambiguity between instruction keywords and shell command text. +FROM : 'FROM' { if (!atLineStart) setType(UNQUOTED_TEXT); else afterFrom = true; atLineStart = false; }; +RUN : 'RUN' { if (!atLineStart) setType(UNQUOTED_TEXT); atLineStart = false; }; +// CMD is a keyword at line start (CMD instruction) or after HEALTHCHECK +CMD : 'CMD' { if (!atLineStart && !afterHealthcheck) setType(UNQUOTED_TEXT); atLineStart = false; afterHealthcheck = false; }; +// NONE is only a keyword after HEALTHCHECK +NONE : 'NONE' { if (!afterHealthcheck) setType(UNQUOTED_TEXT); atLineStart = false; afterHealthcheck = false; }; +LABEL : 'LABEL' { if (!atLineStart) setType(UNQUOTED_TEXT); atLineStart = false; }; +EXPOSE : 'EXPOSE' { if (!atLineStart) setType(UNQUOTED_TEXT); atLineStart = false; }; +ENV : 'ENV' { if (!atLineStart) setType(UNQUOTED_TEXT); atLineStart = false; }; +ADD : 'ADD' { if (!atLineStart) setType(UNQUOTED_TEXT); atLineStart = false; }; +COPY : 'COPY' { if (!atLineStart) setType(UNQUOTED_TEXT); atLineStart = false; }; +ENTRYPOINT : 'ENTRYPOINT' { if (!atLineStart) setType(UNQUOTED_TEXT); atLineStart = false; }; +VOLUME : 'VOLUME' { if (!atLineStart) setType(UNQUOTED_TEXT); atLineStart = false; }; +USER : 'USER' { if (!atLineStart) setType(UNQUOTED_TEXT); atLineStart = false; }; +WORKDIR : 'WORKDIR' { if (!atLineStart) setType(UNQUOTED_TEXT); atLineStart = false; }; +ARG : 'ARG' { if (!atLineStart) setType(UNQUOTED_TEXT); atLineStart = false; }; +// ONBUILD is special: it keeps atLineStart true so the following instruction is recognized +ONBUILD : 'ONBUILD' { if (!atLineStart) setType(UNQUOTED_TEXT); /* atLineStart stays true */ }; +STOPSIGNAL : 'STOPSIGNAL' { if (!atLineStart) setType(UNQUOTED_TEXT); atLineStart = false; }; +// HEALTHCHECK is special: it keeps atLineStart true and sets afterHealthcheck so CMD/NONE are recognized after flags +HEALTHCHECK: 'HEALTHCHECK'{ if (!atLineStart) setType(UNQUOTED_TEXT); else afterHealthcheck = true; /* atLineStart stays true */ }; +SHELL : 'SHELL' { if (!atLineStart) setType(UNQUOTED_TEXT); atLineStart = false; }; +MAINTAINER : 'MAINTAINER' { if (!atLineStart) setType(UNQUOTED_TEXT); atLineStart = false; }; + +// AS is only a keyword after FROM (for stage aliasing) +AS : 'AS' { if (!afterFrom) setType(UNQUOTED_TEXT); atLineStart = false; afterFrom = false; }; + +// Heredoc start - captures < pushMode(HEREDOC_PREAMBLE); + +// Line continuation - HIDDEN in main mode +// Supports both backslash (Linux) and backtick (Windows with # escape=`) +LINE_CONTINUATION : ('\\' | '`') [ \t]* NEWLINE_CHAR -> channel(HIDDEN); + +// JSON array delimiters (for exec form) - no mode switching, handled in parser +LBRACKET : '[' { atLineStart = false; }; +RBRACKET : ']' { atLineStart = false; }; +COMMA : ',' { atLineStart = false; }; + +// Assignment (used in ENV, ARG, LABEL, etc.) +EQUALS : '=' { if (!afterHealthcheck) atLineStart = false; }; + +// Flag with optional value: --name or --name=value +// Captures the entire flag as a single token, stopping at whitespace +// This avoids the greedy flagValue+ parsing issue while keeping shell commands working +FLAG : '--' [a-zA-Z] [a-zA-Z0-9_-]* ('=' ~[ \t\r\n]+)? { if (!afterHealthcheck) atLineStart = false; }; + +// Standalone -- (double dash without flag name) - used in shell commands +DASH_DASH : '--' { if (!afterHealthcheck) atLineStart = false; }; + +// Unquoted text fragment (to be used in UNQUOTED_TEXT) +// This matches text that doesn't start with -- or << +// Note: < is excluded to allow HEREDOC_START (<<) to match +fragment UNQUOTED_CHAR : ~[ \t\r\n\\"'$[\]=<]; +fragment ESCAPED_CHAR : '\\' .; + +// String literals +// Double-quoted strings support escape sequences, line continuation, and bare newlines +// Backtick followed by whitespace+newline is continuation; standalone backtick is regular char +// Bare newlines are allowed (e.g., comment lines inside PowerShell strings don't need trailing backtick) +DOUBLE_QUOTED_STRING : '"' ( ESCAPE_SEQUENCE | INLINE_CONTINUATION | '`' | [\r\n] | ~["\\\r\n`] )* '"' { if (!afterHealthcheck) atLineStart = false; }; +// Single-quoted strings in shell are literal - no escape processing inside +// But they DO support line continuation (backslash or backtick followed by newline) +// Bare newlines are also allowed for multi-line strings +SINGLE_QUOTED_STRING : '\'' ( INLINE_CONTINUATION | [\r\n] | ~['\r\n] )* '\'' { if (!afterHealthcheck) atLineStart = false; }; + +// Inline line continuation (inside strings) - backtick or backslash followed by newline +fragment INLINE_CONTINUATION : ('\\' | '`') [ \t]* [\r\n]+; + +fragment ESCAPE_SEQUENCE + : '\\' ~[\r\n] // Backslash followed by any char except newline (includes \n, \t, \\, \", Windows paths like \P) + ; + +fragment HEX_DIGIT : [0-9A-F]; + +// Environment variable reference +ENV_VAR : ('$' '{' [A-Z_][A-Z0-9_]* ( ':-' | ':+' | ':' )? ~[}]* '}' | '$' [A-Z_][A-Z0-9_]*) { atLineStart = false; }; + +// Special shell variables ($!, $$, $?, $#, $@, $*, $0-$9) +SPECIAL_VAR : '$' [!$?#@*0-9] { atLineStart = false; }; + +// Command substitution $(command) or $((arithmetic)) +// Handles nested parentheses by counting them +COMMAND_SUBST : '$(' ( COMMAND_SUBST | ~[()] | '(' COMMAND_SUBST_INNER* ')' )* ')' { atLineStart = false; }; +fragment COMMAND_SUBST_INNER : COMMAND_SUBST | ~[()]; + +// Backtick command substitution `command` +// First char after backtick must NOT be whitespace/newline (which would be line continuation) +// Content cannot span newlines (backtick command substitution doesn't support that) +BACKTICK_SUBST : '`' ~[ \t\r\n`] ~[`\r\n]* '`' { atLineStart = false; }; + +// Unquoted text (arguments, file paths, etc.) +// This should be after more specific tokens +// Note: comma is NOT excluded here - it's only special in JSON arrays +// We structure this to not match text starting with -- (so DASH_DASH can match first) +// Also exclude < from starting char to allow HEREDOC_START (<<) to match +UNQUOTED_TEXT + : ( ~[-< \t\r\n\\"'$[\]=] ( UNQUOTED_CHAR | ESCAPED_CHAR )* // Start with non-hyphen, non-<, non-space + | '-' ~[- \t\r\n\\"'$[\]=<] ( UNQUOTED_CHAR | ESCAPED_CHAR )* // Single hyphen followed by non-hyphen, non-space + | '-' // Just a hyphen by itself + | '<' ~[< \t\r\n\\"'$[\]=] ( UNQUOTED_CHAR | ESCAPED_CHAR )* // Single < followed by non-< + | '<' // Just a < by itself + | ESCAPED_CHAR ( UNQUOTED_CHAR | ESCAPED_CHAR )* // Start with escaped char (e.g., \; in find -exec) + ) { if (!afterHealthcheck) atLineStart = false; } + ; + +// Whitespace - HIDDEN in main mode +WS : WS_CHAR+ -> channel(HIDDEN); + +fragment WS_CHAR : [ \t]; + +// Newlines - HIDDEN in main mode, reset state for next line +NEWLINE : NEWLINE_CHAR+ { atLineStart = true; afterFrom = false; afterHealthcheck = false; } -> channel(HIDDEN); + +fragment NEWLINE_CHAR : [\r\n]; + +// ---------------------------------------------------------------------------------------------- +// HEREDOC_PREAMBLE mode - for parsing shell command preamble after heredoc marker(s) +// The heredoc identifier (e.g., EOF) is already captured in HEREDOC_START +// This mode handles the shell command text including additional heredoc markers for multi-heredoc. +// ---------------------------------------------------------------------------------------------- +mode HEREDOC_PREAMBLE; + +// Line continuation in preamble - stay in HEREDOC_PREAMBLE mode +HP_LINE_CONTINUATION : ('\\' | '`') [ \t]* '\n' -> channel(HIDDEN); + +// Newline without continuation - transition to HEREDOC mode for body content +HP_NEWLINE : '\n' -> type(NEWLINE), mode(HEREDOC); + +HP_WS : [ \t\r\u000C]+ -> channel(HIDDEN); +HP_COMMENT : '/*' .*? '*/' -> channel(HIDDEN); +HP_LINE_COMMENT : ('//' | '#') ~[\r\n]* '\r'? -> channel(HIDDEN); + +// Additional heredoc marker in preamble (for multi-heredoc support) +HP_HEREDOC_START : '<<' '-'? [A-Z_][A-Z0-9_]* { + // Extract and store the heredoc marker identifier in FIFO order + String text = getText(); + int prefixLen = text.charAt(2) == '-' ? 3 : 2; + String marker = text.substring(prefixLen); + heredocIdentifiers.add(marker); +} -> type(HEREDOC_START); + +// Any text on the heredoc line after the marker (destination paths, interpreter names, shell commands, etc.) +// Exclude < to allow HP_HEREDOC_START to match << +// Exclude \ and ` to allow HP_LINE_CONTINUATION to match +HP_UNQUOTED_TEXT : ( ~[<\\` \t\r\n]+ + | '<' ~[< \t\r\n] ~[ \t\r\n]* // single < followed by non-< char + | '<' // standalone < + ) -> type(UNQUOTED_TEXT); + +// ---------------------------------------------------------------------------------------------- +// HEREDOC mode - for parsing heredoc content +// Supports multiple heredocs by only popping mode when all markers have been matched. +// ---------------------------------------------------------------------------------------------- +mode HEREDOC; + +H_NEWLINE : '\n' -> type(NEWLINE); + +// Match heredoc content lines - emit as HEREDOC_CONTENT unless it's an ending identifier +// For multi-heredoc, we only popMode when the queue is empty (all markers matched in FIFO order) +HEREDOC_CONTENT : ~[\n]+ +{ + if(!heredocIdentifiers.isEmpty() && getText().equals(heredocIdentifiers.peek())) { + setType(UNQUOTED_TEXT); + heredocIdentifiers.poll(); // Remove from front of queue (FIFO) + // Only pop mode when all heredoc markers have been matched + if(heredocIdentifiers.isEmpty()) { + popMode(); + atLineStart = true; // After heredoc ends, next line is at line start + } + } +}; + diff --git a/rewrite-docker/src/main/antlr/DockerParser.g4 b/rewrite-docker/src/main/antlr/DockerParser.g4 new file mode 100644 index 0000000000..362b474159 --- /dev/null +++ b/rewrite-docker/src/main/antlr/DockerParser.g4 @@ -0,0 +1,420 @@ +// $antlr-format alignTrailingComments true, columnLimit 150, minEmptyLines 1, maxEmptyLinesToKeep 1, reflowComments false, useTab false +// $antlr-format allowShortRulesOnASingleLine false, allowShortBlocksOnASingleLine true, alignSemicolons hanging, alignColons hanging + +parser grammar DockerParser; + +options { + tokenVocab = DockerLexer; +} + +// Root rule +dockerfile + : parserDirective* globalArgs stage+ EOF + ; + +parserDirective + : PARSER_DIRECTIVE + ; + +// Global ARG instructions before first FROM +globalArgs + : argInstruction* + ; + +// A build stage starting with FROM +stage + : fromInstruction stageInstruction* + ; + +// Instructions allowed within a stage (everything except FROM and global ARG) +stageInstruction + : runInstruction + | cmdInstruction + | labelInstruction + | exposeInstruction + | envInstruction + | addInstruction + | copyInstruction + | entrypointInstruction + | volumeInstruction + | userInstruction + | workdirInstruction + | argInstruction + | onbuildInstruction + | stopsignalInstruction + | healthcheckInstruction + | shellInstruction + | maintainerInstruction + ; + +// Legacy: kept for backward compatibility if needed elsewhere +instruction + : fromInstruction + | stageInstruction + ; + +fromInstruction + : FROM flags? imageName ( AS stageName )? + ; + +runInstruction + : RUN flags? ( execForm | shellForm | heredoc ) + ; + +cmdInstruction + : CMD ( execForm | shellForm ) + ; + +labelInstruction + : LABEL labelPairs + ; + +exposeInstruction + : EXPOSE portList + ; + +envInstruction + : ENV envPairs + ; + +addInstruction + : ADD flags? ( heredoc | jsonArray | sourceList destination ) + ; + +copyInstruction + : COPY flags? ( heredoc | jsonArray | sourceList destination ) + ; + +entrypointInstruction + : ENTRYPOINT ( execForm | shellForm ) + ; + +volumeInstruction + : VOLUME ( jsonArray | pathList ) + ; + +userInstruction + : USER userSpec + ; + +workdirInstruction + : WORKDIR path + ; + +argInstruction + : ARG argName ( EQUALS argValue )? + ; + +onbuildInstruction + : ONBUILD instruction + ; + +stopsignalInstruction + : STOPSIGNAL signal + ; + +healthcheckInstruction + : HEALTHCHECK NONE // Disable health checks + | HEALTHCHECK healthcheckOptions? CMD ( execForm | shellForm ) // Health check command + ; + +// HEALTHCHECK-specific options - uses FLAG token like regular flags +healthcheckOptions + : healthcheckOption+ + ; + +healthcheckOption + : FLAG + ; + +shellInstruction + : SHELL jsonArray + ; + +maintainerInstruction + : MAINTAINER text + ; + +// Common elements +flags + : flag+ + ; + +// Flag token captures entire flag: --name or --name=value +// The lexer handles stopping at whitespace, so no greedy parsing issues +flag + : FLAG + ; + +execForm + : jsonArray + ; + +shellForm + : shellFormText + ; + +// Text in shell form commands +// Note: Instruction keywords (RUN, ADD, COPY, AS, CMD, etc.) become UNQUOTED_TEXT here +// because they are only recognized as keyword tokens in specific contexts. +shellFormText + : shellFormTextElement+ + ; + +shellFormTextElement + : UNQUOTED_TEXT + | DOUBLE_QUOTED_STRING + | SINGLE_QUOTED_STRING + | ENV_VAR + | COMMAND_SUBST // Allow $(command) in shell commands + | BACKTICK_SUBST // Allow `command` in shell commands + | SPECIAL_VAR // Allow $!, $$, $?, etc. in shell commands + | EQUALS + | FLAG // Allow --option or --option=value in shell commands + | DASH_DASH + | LBRACKET // Allow [ in shell commands (e.g., if [ -f file ]) + | RBRACKET // Allow ] in shell commands + | COMMA // Allow , in shell commands + ; + +// Unified heredoc structure supporting both single and multiple heredocs +// Single: RUN <file1 && <file2 ... EOF1 ... EOF2 +heredoc + : heredocPreamble NEWLINE heredocBody+ + ; + +// Shell command preamble containing heredoc marker(s) and optional shell commands +// For single heredoc: just "<file1 && <file2" +heredocPreamble + : HEREDOC_START preambleElement* ( HEREDOC_START preambleElement* )* + ; + +// Elements that can appear in the heredoc preamble (shell command text, destination paths) +preambleElement + : UNQUOTED_TEXT + | DOUBLE_QUOTED_STRING + | SINGLE_QUOTED_STRING + | ENV_VAR + | COMMAND_SUBST + | BACKTICK_SUBST + | SPECIAL_VAR + | EQUALS + | FLAG + | DASH_DASH + | LBRACKET + | RBRACKET + | COMMA + ; + +// A single heredoc body (content + closing marker) +heredocBody + : heredocContent heredocEnd + ; + +heredocContent + : ( NEWLINE | HEREDOC_CONTENT )* + ; + +heredocEnd + : UNQUOTED_TEXT + ; + +jsonArray + : LBRACKET jsonArrayElements? RBRACKET + ; + +jsonArrayElements + : jsonString ( COMMA jsonString )* + ; + +jsonString + : DOUBLE_QUOTED_STRING + ; + +imageName + : text + ; + +stageName + : UNQUOTED_TEXT + ; + +labelPairs + : labelPair+ + ; + +labelPair + : labelKey EQUALS labelValue // New format: key=value + | labelKey labelOldValue // Old format: key value + ; + +// Label key - instruction keywords become UNQUOTED_TEXT since they're not at line start +labelKey + : UNQUOTED_TEXT | DOUBLE_QUOTED_STRING | SINGLE_QUOTED_STRING + ; + +labelValue + : UNQUOTED_TEXT | DOUBLE_QUOTED_STRING | SINGLE_QUOTED_STRING + ; + +// Value in old-style LABEL (rest of line after key) +// Instruction keywords are UNQUOTED_TEXT here (not at line start) +labelOldValue + : labelOldValueElement+ + ; + +labelOldValueElement + : UNQUOTED_TEXT + | DOUBLE_QUOTED_STRING + | SINGLE_QUOTED_STRING + | ENV_VAR + | COMMAND_SUBST + | BACKTICK_SUBST + | SPECIAL_VAR + | EQUALS + | FLAG + | DASH_DASH + | LBRACKET + | RBRACKET + | COMMA + ; + +portList + : port+ + ; + +port + : UNQUOTED_TEXT + | ENV_VAR // Allow environment variables (e.g., EXPOSE ${PORT}) + | COMMAND_SUBST // Allow $(command) + | BACKTICK_SUBST // Allow `command` + | SPECIAL_VAR // Allow $!, $$, etc. + ; + +envPairs + : envPair+ + ; + +envPair + : envKey EQUALS envValueEquals // New form: KEY=value (no = in value) + | envKey envValueSpace // Old form: KEY value (rest of line, can have =) + ; + +// Env key - instruction keywords become UNQUOTED_TEXT (not at line start) +envKey + : UNQUOTED_TEXT + ; + +envValueEquals + : envTextEquals + ; + +envValueSpace + : text + ; + +envTextEquals + : envTextElementEquals+ + ; + +envTextElementEquals + : UNQUOTED_TEXT + | DOUBLE_QUOTED_STRING + | SINGLE_QUOTED_STRING + | ENV_VAR + | COMMAND_SUBST + | BACKTICK_SUBST + | SPECIAL_VAR + // NOTE: EQUALS is explicitly NOT included to allow multiple KEY=value pairs + ; + +// For COPY/ADD: each sourcePath is a separate source +// The parser will group adjacent tokens without whitespace into single arguments +sourceList + : sourcePath+ + ; + +sourcePath + : UNQUOTED_TEXT + | DOUBLE_QUOTED_STRING + | SINGLE_QUOTED_STRING + | ENV_VAR + | COMMAND_SUBST + | BACKTICK_SUBST + | SPECIAL_VAR + ; + +// Destination is the last path element +destination + : destinationPath + ; + +destinationPath + : UNQUOTED_TEXT + | DOUBLE_QUOTED_STRING + | SINGLE_QUOTED_STRING + | ENV_VAR + | COMMAND_SUBST + | BACKTICK_SUBST + | SPECIAL_VAR + ; + +path + : text + ; + +pathList + : volumePath+ + ; + +volumePath + : UNQUOTED_TEXT + | DOUBLE_QUOTED_STRING + | SINGLE_QUOTED_STRING + | ENV_VAR // Allow environment variables (e.g., VOLUME ${DATA_DIR}) + | COMMAND_SUBST // Allow $(command) + | BACKTICK_SUBST // Allow `command` + | SPECIAL_VAR // Allow $!, $$, etc. + ; + +userSpec + : text + ; + +argName + : UNQUOTED_TEXT + ; + +argValue + : text + ; + +signal + : UNQUOTED_TEXT + ; + +text + : textElement+ + ; + +// Generic text element - used for paths, image names, arg values, etc. +// Instruction keywords and contextual keywords (AS, CMD, NONE) become UNQUOTED_TEXT +// when not in their specific contexts. +textElement + : UNQUOTED_TEXT + | DOUBLE_QUOTED_STRING + | SINGLE_QUOTED_STRING + | ENV_VAR + | COMMAND_SUBST // Allow $(command) in text + | BACKTICK_SUBST // Allow `command` in text + | SPECIAL_VAR // Allow $!, $$, $?, etc. in text + | EQUALS // Allow = in shell form text (e.g., ENV_VAR=value in RUN commands) + | FLAG // Allow --option or --option=value in text + | DASH_DASH // Allow -- in shell form text (e.g., --option in shell commands) + | LBRACKET // Allow [ in text (e.g., shell test expressions) + | RBRACKET // Allow ] in text + | COMMA // Allow , in text + ; + diff --git a/rewrite-docker/src/main/java/org/openrewrite/docker/AddOrUpdateLabel.java b/rewrite-docker/src/main/java/org/openrewrite/docker/AddOrUpdateLabel.java new file mode 100644 index 0000000000..d20c424f12 --- /dev/null +++ b/rewrite-docker/src/main/java/org/openrewrite/docker/AddOrUpdateLabel.java @@ -0,0 +1,200 @@ +/* + * Copyright 2026 the original author or authors. + *

+ * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * https://www.apache.org/licenses/LICENSE-2.0 + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.openrewrite.docker; + +import lombok.EqualsAndHashCode; +import lombok.Value; +import org.jspecify.annotations.Nullable; +import org.openrewrite.*; +import org.openrewrite.docker.tree.Docker; +import org.openrewrite.docker.tree.Space; +import org.openrewrite.internal.ListUtils; +import org.openrewrite.marker.Markers; + +import java.util.List; +import java.util.concurrent.atomic.AtomicBoolean; + +import static java.util.Collections.singletonList; +import static org.openrewrite.Tree.randomId; + +@Value +@EqualsAndHashCode(callSuper = false) +public class AddOrUpdateLabel extends Recipe { + + @Option(displayName = "Label key", + description = "The key of the label to add.", + example = "org.opencontainers.image.version") + String key; + + @Option(displayName = "Label value", + description = "The value of the label.", + example = "1.0.0") + String value; + + @Option(displayName = "Overwrite existing", + description = "If true, overwrite the label if it already exists. If false, skip if exists. Defaults to true.", + required = false) + @Nullable + Boolean overwriteExisting; + + @Option(displayName = "Stage name", + description = "Only add the label to this build stage. If null, adds to the final stage only.", + example = "final", + required = false) + @Nullable + String stageName; + + @Override + public String getDisplayName() { + return "Add Docker LABEL instruction"; + } + + @Override + public String getDescription() { + return "Adds or updates a LABEL instruction in a Dockerfile. By default, adds to the final stage only."; + } + + @Override + public TreeVisitor getVisitor() { + return Preconditions.or(new UpdateLabelVisitor(), new AddLabelVisitor()); + } + + /** + * Only update existing labels. + */ + private class UpdateLabelVisitor extends DockerIsoVisitor { + @Override + public Docker.Stage visitStage(Docker.Stage stage, ExecutionContext ctx) { + if (stageName != null && !stageName.equals(stage.getFrom().getAs() != null ? stage.getFrom().getAs().getName().getText() : null)) { + return stage; // Skip this stage + } + return super.visitStage(stage, ctx); // Update existing labels anywhere + } + + @Override + public Docker.Label.LabelPair visitLabelPair(Docker.Label.LabelPair pair, ExecutionContext ctx) { + if (key.equals(extractText(pair.getKey()))) { + boolean shouldOverwrite = overwriteExisting == null || overwriteExisting; + return shouldOverwrite && !value.equals(extractText(pair.getValue())) ? + pair.withValue(createArgument(value, pair.getValue())) : pair; + } + return super.visitLabelPair(pair, ctx); + } + } + + /** + * Only add new labels. + */ + private class AddLabelVisitor extends DockerIsoVisitor { + @Override + public Docker.Stage visitStage(Docker.Stage stage, ExecutionContext ctx) { + if (stageName == null) { // Only modify final stage by default + List stages = getCursor().getParentTreeCursor().getValue().getStages(); + return stage == stages.get(stages.size() - 1) ? addNewLabel(stage) : stage; + } + Docker.From.As as = stage.getFrom().getAs(); + return stageName.equals(as != null ? as.getName().getText() : null) ? addNewLabel(stage) : stage; + } + + private Docker.Stage addNewLabel(Docker.Stage stage) { + if (hasLabel(stage)) { + return stage; // Prevent adding duplicate label + } + return stage.withInstructions(ListUtils.insert( + stage.getInstructions(), + createLabel(), + findLabelInsertPosition(stage))); + } + + private boolean hasLabel(Docker.Stage stage) { + return new DockerIsoVisitor() { + @Override + public Docker.Label.LabelPair visitLabelPair(Docker.Label.LabelPair pair, AtomicBoolean matchFound) { + if (!matchFound.get() && key.equals(extractText(pair.getKey()))) { + matchFound.set(true); + } + return pair; + } + }.reduce(stage, new AtomicBoolean(false)).get(); + } + + private Docker.Label createLabel() { + Docker.Label.LabelPair pair = new Docker.Label.LabelPair( + Tree.randomId(), + Space.SINGLE_SPACE, + Markers.EMPTY, + createArgument(key, null), + true, // hasEquals - use modern format with equals sign + createArgument(value, null) + ); + return new Docker.Label(randomId(), Space.format("\n"), Markers.EMPTY, "LABEL", singletonList(pair)); + } + + private int findLabelInsertPosition(Docker.Stage stage) { + int lastLabelIndex = -1; // Insert at start if no labels found + for (int i = 0; i < stage.getInstructions().size(); i++) { + Docker.Instruction inst = stage.getInstructions().get(i); + if (inst instanceof Docker.Label) { + lastLabelIndex = i; // After any existing labels + } + } + return lastLabelIndex + 1; + } + } + + private static @Nullable String extractText(Docker.@Nullable Argument arg) { + if (arg == null) { + return null; + } + StringBuilder builder = new StringBuilder(); + for (Docker.ArgumentContent content : arg.getContents()) { + if (content instanceof Docker.Literal) { + builder.append(((Docker.Literal) content).getText()); + } else if (content instanceof Docker.EnvironmentVariable) { + Docker.EnvironmentVariable env = (Docker.EnvironmentVariable) content; + // Include the variable reference as-is (e.g., ${VAR} or $VAR) + if (env.isBraced()) { + builder.append("${").append(env.getName()).append("}"); + } else { + builder.append("$").append(env.getName()); + } + } + } + return builder.toString(); + } + + private static Docker.Argument createArgument(String text, Docker.@Nullable Argument original) { + // Quote if contains spaces or special characters + boolean needsQuotes = text.contains(" ") || text.contains("="); + Docker.ArgumentContent content; + if (needsQuotes) { + // Preserve quote style from original if available + Docker.Literal.QuoteStyle quoteStyle = Docker.Literal.QuoteStyle.DOUBLE; + if (original != null) { + for (Docker.ArgumentContent c : original.getContents()) { + if (c instanceof Docker.Literal && ((Docker.Literal) c).isQuoted()) { + quoteStyle = ((Docker.Literal) c).getQuoteStyle(); + break; + } + } + } + content = new Docker.Literal(randomId(), Space.EMPTY, Markers.EMPTY, text, quoteStyle); + } else { + content = new Docker.Literal(randomId(), Space.EMPTY, Markers.EMPTY, text, null); + } + return new Docker.Argument(randomId(), Space.EMPTY, Markers.EMPTY, singletonList(content)); + } +} diff --git a/rewrite-docker/src/main/java/org/openrewrite/docker/Assertions.java b/rewrite-docker/src/main/java/org/openrewrite/docker/Assertions.java new file mode 100644 index 0000000000..2e083bfaf1 --- /dev/null +++ b/rewrite-docker/src/main/java/org/openrewrite/docker/Assertions.java @@ -0,0 +1,94 @@ +/* + * Copyright 2026 the original author or authors. + *

+ * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * https://www.apache.org/licenses/LICENSE-2.0 + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.openrewrite.docker; + +import org.intellij.lang.annotations.Language; +import org.jspecify.annotations.Nullable; +import org.openrewrite.SourceFile; +import org.openrewrite.docker.tree.Docker; +import org.openrewrite.docker.tree.Space; +import org.openrewrite.test.SourceSpec; +import org.openrewrite.test.SourceSpecs; +import org.openrewrite.test.TypeValidation; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.Consumer; + +public class Assertions { + private Assertions() { + } + + public static SourceSpecs docker(@Language("dockerfile") @Nullable String before) { + return docker(before, s -> { + }); + } + + public static SourceSpecs docker(@Language("dockerfile") @Nullable String before, + Consumer> spec) { + SourceSpec dockerfile = new SourceSpec<>( + Docker.File.class, + null, + DockerParser.builder(), + before, + Assertions::validate, + ctx -> { + } + ); + spec.accept(dockerfile); + return dockerfile; + } + + public static SourceSpecs docker(@Language("dockerfile") @Nullable String before, + @Language("dockerfile") @Nullable String after) { + return docker(before, after, s -> { + }); + } + + public static SourceSpecs docker(@Language("dockerfile") @Nullable String before, + @Language("dockerfile") @Nullable String after, + Consumer> spec) { + SourceSpec dockerfile = new SourceSpec<>( + Docker.File.class, + null, + DockerParser.builder(), + before, + Assertions::validate, + ctx -> { + } + ).after(s -> after); + spec.accept(dockerfile); + return dockerfile; + } + + private static SourceFile validate(SourceFile sf, TypeValidation tv) { + if (!tv.allowNonWhitespaceInWhitespace()) { + List elementsWithNonBlankWhitespace = new DockerIsoVisitor>() { + @Override + public Space visitSpace(Space space, List elements) { + if (!space.getWhitespace().trim().isEmpty()) { + elements.add(getCursor().firstEnclosingOrThrow(Docker.class)); + } + return super.visitSpace(space, elements); + } + }.reduce(sf, new ArrayList<>()); + if (!elementsWithNonBlankWhitespace.isEmpty()) { + throw new AssertionError("Expected no non-whitespace in whitespace, but found: " + elementsWithNonBlankWhitespace); + } + } + return sf; + } +} diff --git a/rewrite-docker/src/main/java/org/openrewrite/docker/ChangeBaseImage.java b/rewrite-docker/src/main/java/org/openrewrite/docker/ChangeBaseImage.java new file mode 100644 index 0000000000..08e756a1bd --- /dev/null +++ b/rewrite-docker/src/main/java/org/openrewrite/docker/ChangeBaseImage.java @@ -0,0 +1,309 @@ +/* + * Copyright 2026 the original author or authors. + *

+ * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * https://www.apache.org/licenses/LICENSE-2.0 + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.openrewrite.docker; + +import lombok.EqualsAndHashCode; +import lombok.Value; +import org.jspecify.annotations.Nullable; +import org.openrewrite.ExecutionContext; +import org.openrewrite.Option; +import org.openrewrite.Recipe; +import org.openrewrite.TreeVisitor; +import org.openrewrite.docker.tree.Docker; +import org.openrewrite.docker.tree.Space; +import org.openrewrite.internal.ListUtils; +import org.openrewrite.internal.StringUtils; +import org.openrewrite.marker.Markers; + +import java.util.List; +import java.util.Objects; + +import static java.util.Collections.singletonList; +import static java.util.Objects.requireNonNull; +import static org.openrewrite.Tree.randomId; + +@Value +@EqualsAndHashCode(callSuper = false) +public class ChangeBaseImage extends Recipe { + + @Option(displayName = "Old image name", + description = "The old image name to replace. Supports glob patterns.", + example = "ubuntu:20.04") + String oldImageName; + + @Option(displayName = "New image name", + description = "The new image name to use.", + example = "ubuntu:22.04") + String newImageName; + + @Option(displayName = "Old platform", + description = "Only change images with this platform. If null, matches any platform.", + example = "linux/amd64", + required = false) + @Nullable + String oldPlatform; + + @Option(displayName = "New platform", + description = "Set the platform to this value. If null and oldPlatform is specified, removes the platform flag from matched images. If both oldPlatform and newPlatform are null, platform flags are preserved.", + example = "linux/arm64", + required = false) + @Nullable + String newPlatform; + + @Override + public String getDisplayName() { + return "Change Docker base image"; + } + + @Override + public String getDescription() { + return "Change the base image in a Dockerfile FROM instruction."; + } + + @Override + public TreeVisitor getVisitor() { + return new DockerIsoVisitor() { + @Override + public Docker.From visitFrom(Docker.From from, ExecutionContext ctx) { + // Visit children first + Docker.From f = super.visitFrom(from, ctx); + + // Reconstruct the full image name from imageName, tag, and digest + StringBuilder imageTextBuilder = new StringBuilder(); + + // Add image name + for (Docker.ArgumentContent content : f.getImageName().getContents()) { + if (content instanceof Docker.Literal) { + imageTextBuilder.append(((Docker.Literal) content).getText()); + } else if (content instanceof Docker.EnvironmentVariable) { + // For environment variables, we can't know the actual value, so skip matching + return f; + } + } + + // Add tag or digest + if (f.getTag() != null) { + imageTextBuilder.append(":"); + for (Docker.ArgumentContent content : f.getTag().getContents()) { + if (content instanceof Docker.Literal) { + imageTextBuilder.append(((Docker.Literal) content).getText()); + } else if (content instanceof Docker.EnvironmentVariable) { + return f; + } + } + } else if (f.getDigest() != null) { + imageTextBuilder.append("@"); + for (Docker.ArgumentContent content : f.getDigest().getContents()) { + if (content instanceof Docker.Literal) { + imageTextBuilder.append(((Docker.Literal) content).getText()); + } else if (content instanceof Docker.EnvironmentVariable) { + return f; + } + } + } + + String imageText = imageTextBuilder.toString(); + + if (!StringUtils.matchesGlob(imageText, oldImageName)) { + return f; + } + + // Get the current platform flag value, if any + String currentPlatform = getPlatformFlag(f); + + // Check if oldPlatform is specified and matches + if (oldPlatform != null && !oldPlatform.equals(currentPlatform)) { + return f; + } + + boolean imageChanged = !imageText.equals(newImageName); + // Only consider platform changed if oldPlatform or newPlatform was explicitly set + boolean shouldChangePlatform = oldPlatform != null || newPlatform != null; + boolean platformChanged = shouldChangePlatform && !Objects.equals(currentPlatform, newPlatform); + + if (!imageChanged && !platformChanged) { + return f; + } + + Docker.From result = f; + + // Update platform flag if needed + if (platformChanged) { + result = updatePlatformFlag(result, newPlatform); + } + + // Update image if needed + if (imageChanged) { + // Check if the original was a single content item (e.g., a single quoted string) + boolean wasSingleContent = f.getImageName().getContents().size() == 1 && + f.getTag() == null && f.getDigest() == null; + + if (wasSingleContent) { + // Keep as a single content item (don't split) + Docker.Literal.QuoteStyle quoteStyle = getQuoteStyle(f.getImageName()); + Docker.ArgumentContent newContent = createContent(newImageName, quoteStyle); + Docker.Argument newImageArg = f.getImageName().withContents(singletonList(newContent)); + return result.withImageName(newImageArg); + } + + // Split into components + @Nullable String[] parts = parseNewImageName(newImageName); + String newImage = requireNonNull(parts[0]); + String newTag = parts[1]; + String newDigest = parts[2]; + + // Check if the original used quotes + Docker.Literal.QuoteStyle quoteStyle = getQuoteStyle(f.getImageName()); + + // Create new image name argument + Docker.ArgumentContent newImageContent = createContent(newImage, quoteStyle); + Docker.Argument newImageArg = f.getImageName().withContents(singletonList(newImageContent)); + result = result.withImageName(newImageArg); + + // Create new tag argument if present + if (newTag != null) { + Docker.ArgumentContent newTagContent = createContent(newTag, quoteStyle); + Docker.Argument newTagArg = new Docker.Argument(randomId(), Space.EMPTY, Markers.EMPTY, singletonList(newTagContent)); + return result.withTag(newTagArg).withDigest(null); + } + if (newDigest != null) { + Docker.ArgumentContent newDigestContent = createContent(newDigest, quoteStyle); + Docker.Argument newDigestArg = new Docker.Argument(randomId(), Space.EMPTY, Markers.EMPTY, singletonList(newDigestContent)); + return result.withDigest(newDigestArg).withTag(null); + } + return result.withTag(null).withDigest(null); + } + + return result; + } + }; + } + + private @Nullable String [] parseNewImageName(String fullImageName) { + String imageName; + String tag = null; + String digest = null; + + // Check for digest first (takes precedence) + int atIndex = fullImageName.indexOf('@'); + if (atIndex > 0) { + imageName = fullImageName.substring(0, atIndex); + digest = fullImageName.substring(atIndex + 1); + } else { + // Check for tag + int colonIndex = fullImageName.lastIndexOf(':'); + if (colonIndex > 0) { + imageName = fullImageName.substring(0, colonIndex); + tag = fullImageName.substring(colonIndex + 1); + } else { + imageName = fullImageName; + } + } + + return new @Nullable String[]{imageName, tag, digest}; + } + + private Docker.Literal.@Nullable QuoteStyle getQuoteStyle(Docker.Argument arg) { + for (Docker.ArgumentContent content : arg.getContents()) { + if (content instanceof Docker.Literal) { + Docker.Literal.QuoteStyle style = ((Docker.Literal) content).getQuoteStyle(); + if (style != null) { + return style; + } + } + } + return null; + } + + private Docker.ArgumentContent createContent(String text, Docker.Literal.@Nullable QuoteStyle quoteStyle) { + return new Docker.Literal(randomId(), Space.EMPTY, Markers.EMPTY, text, quoteStyle); + } + + private @Nullable String getPlatformFlag(Docker.From from) { + if (from.getFlags() == null) { + return null; + } + + for (Docker.Flag flag : from.getFlags()) { + if ("platform".equals(flag.getName()) && flag.getValue() != null) { + for (Docker.ArgumentContent content : flag.getValue().getContents()) { + if (content instanceof Docker.Literal) { + return ((Docker.Literal) content).getText(); + } + } + } + } + return null; + } + + private Docker.From updatePlatformFlag(Docker.From from, @Nullable String platform) { + List oldFlags = from.getFlags(); + + // Update or remove existing platform flag + List newFlags = ListUtils.map(oldFlags, flag -> { + if ("platform".equals(flag.getName())) { + if (platform != null) { + // Update existing platform flag using withers + return flag.withValue(updatePlatformValue(flag.getValue(), platform)); + } + // If platform is null, return null to remove this flag + return null; + } + return flag; + }); + if (oldFlags != newFlags || platform == null) { + return from.withFlags(newFlags); + } + + // Add new platform flag if it wasn't found and platform is not null + Docker.Flag platformFlag = new Docker.Flag( + randomId(), + oldFlags != null && !oldFlags.isEmpty() ? oldFlags.get(0).getPrefix() : Space.SINGLE_SPACE, + from.getMarkers(), + "platform", + createPlatformValue(platform, Space.EMPTY) + ); + return from.withFlags(ListUtils.concat(platformFlag, newFlags)); + } + + private Docker.Argument updatePlatformValue(Docker.@Nullable Argument existingValue, String platform) { + if (existingValue != null && !existingValue.getContents().isEmpty()) { + // Update existing value using withers + Docker.ArgumentContent firstContent = existingValue.getContents().get(0); + if (firstContent instanceof Docker.Literal) { + Docker.Literal updated = ((Docker.Literal) firstContent).withText(platform); + return existingValue.withContents(singletonList(updated)); + } + } + // Fallback: create new value + return createPlatformValue(platform, existingValue != null ? existingValue.getPrefix() : Space.EMPTY); + } + + private Docker.Argument createPlatformValue(String platform, Space prefix) { + return new Docker.Argument( + randomId(), + prefix, + Markers.EMPTY, + singletonList(new Docker.Literal( + randomId(), + Space.EMPTY, + Markers.EMPTY, + platform, + null + )) + ); + } +} diff --git a/rewrite-docker/src/main/java/org/openrewrite/docker/DockerIsoVisitor.java b/rewrite-docker/src/main/java/org/openrewrite/docker/DockerIsoVisitor.java new file mode 100644 index 0000000000..3b03a98b3f --- /dev/null +++ b/rewrite-docker/src/main/java/org/openrewrite/docker/DockerIsoVisitor.java @@ -0,0 +1,177 @@ +/* + * Copyright 2026 the original author or authors. + *

+ * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * https://www.apache.org/licenses/LICENSE-2.0 + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.openrewrite.docker; + +import org.jspecify.annotations.Nullable; +import org.openrewrite.docker.tree.Docker; + +public class DockerIsoVisitor

extends DockerVisitor

{ + + @Override + public Docker.File visitFile(Docker.File file, P p) { + return (Docker.File) super.visitFile(file, p); + } + + @Override + public Docker.Stage visitStage(Docker.Stage stage, P p) { + return (Docker.Stage) super.visitStage(stage, p); + } + + @Override + public Docker.From visitFrom(Docker.From from, P p) { + return (Docker.From) super.visitFrom(from, p); + } + + @Override + public Docker.From.@Nullable As visitFromAs(Docker.From.As as, P p) { + return super.visitFromAs(as, p); + } + + @Override + public Docker.Run visitRun(Docker.Run run, P p) { + return (Docker.Run) super.visitRun(run, p); + } + + @Override + public Docker.Add visitAdd(Docker.Add add, P p) { + return (Docker.Add) super.visitAdd(add, p); + } + + @Override + public Docker.Copy visitCopy(Docker.Copy copy, P p) { + return (Docker.Copy) super.visitCopy(copy, p); + } + + @Override + public Docker.Arg visitArg(Docker.Arg arg, P p) { + return (Docker.Arg) super.visitArg(arg, p); + } + + @Override + public Docker.Env visitEnv(Docker.Env env, P p) { + return (Docker.Env) super.visitEnv(env, p); + } + + @Override + public Docker.Env.EnvPair visitEnvPair(Docker.Env.EnvPair pair, P p) { + return super.visitEnvPair(pair, p); + } + + @Override + public Docker.Label visitLabel(Docker.Label label, P p) { + return (Docker.Label) super.visitLabel(label, p); + } + + @Override + public Docker.Label.LabelPair visitLabelPair(Docker.Label.LabelPair pair, P p) { + return super.visitLabelPair(pair, p); + } + + @Override + public Docker.Cmd visitCmd(Docker.Cmd cmd, P p) { + return (Docker.Cmd) super.visitCmd(cmd, p); + } + + @Override + public Docker.Entrypoint visitEntrypoint(Docker.Entrypoint entrypoint, P p) { + return (Docker.Entrypoint) super.visitEntrypoint(entrypoint, p); + } + + @Override + public Docker.Expose visitExpose(Docker.Expose expose, P p) { + return (Docker.Expose) super.visitExpose(expose, p); + } + + @Override + public Docker.Port visitPort(Docker.Port port, P p) { + return (Docker.Port) super.visitPort(port, p); + } + + @Override + public Docker.Volume visitVolume(Docker.Volume volume, P p) { + return (Docker.Volume) super.visitVolume(volume, p); + } + + @Override + public Docker.Shell visitShell(Docker.Shell shell, P p) { + return (Docker.Shell) super.visitShell(shell, p); + } + + @Override + public Docker.Workdir visitWorkdir(Docker.Workdir workdir, P p) { + return (Docker.Workdir) super.visitWorkdir(workdir, p); + } + + @Override + public Docker.User visitUser(Docker.User user, P p) { + return (Docker.User) super.visitUser(user, p); + } + + @Override + public Docker.Stopsignal visitStopsignal(Docker.Stopsignal stopsignal, P p) { + return (Docker.Stopsignal) super.visitStopsignal(stopsignal, p); + } + + @Override + public Docker.Onbuild visitOnbuild(Docker.Onbuild onbuild, P p) { + return (Docker.Onbuild) super.visitOnbuild(onbuild, p); + } + + @Override + public Docker.Healthcheck visitHealthcheck(Docker.Healthcheck healthcheck, P p) { + return (Docker.Healthcheck) super.visitHealthcheck(healthcheck, p); + } + + @Override + public Docker.Maintainer visitMaintainer(Docker.Maintainer maintainer, P p) { + return (Docker.Maintainer) super.visitMaintainer(maintainer, p); + } + + @Override + public Docker.ShellForm visitShellForm(Docker.ShellForm shellForm, P p) { + return (Docker.ShellForm) super.visitShellForm(shellForm, p); + } + + @Override + public Docker.ExecForm visitExecForm(Docker.ExecForm execForm, P p) { + return (Docker.ExecForm) super.visitExecForm(execForm, p); + } + + @Override + public Docker.HeredocForm visitHeredocForm(Docker.HeredocForm heredocForm, P p) { + return (Docker.HeredocForm) super.visitHeredocForm(heredocForm, p); + } + + @Override + public Docker.Flag visitFlag(Docker.Flag flag, P p) { + return (Docker.Flag) super.visitFlag(flag, p); + } + + @Override + public Docker.Argument visitArgument(Docker.Argument argument, P p) { + return (Docker.Argument) super.visitArgument(argument, p); + } + + @Override + public Docker.Literal visitLiteral(Docker.Literal literal, P p) { + return (Docker.Literal) super.visitLiteral(literal, p); + } + + @Override + public Docker.EnvironmentVariable visitEnvironmentVariable(Docker.EnvironmentVariable environmentVariable, P p) { + return (Docker.EnvironmentVariable) super.visitEnvironmentVariable(environmentVariable, p); + } +} diff --git a/rewrite-docker/src/main/java/org/openrewrite/docker/DockerParser.java b/rewrite-docker/src/main/java/org/openrewrite/docker/DockerParser.java new file mode 100644 index 0000000000..8f07f6a124 --- /dev/null +++ b/rewrite-docker/src/main/java/org/openrewrite/docker/DockerParser.java @@ -0,0 +1,127 @@ +/* + * Copyright 2026 the original author or authors. + *

+ * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * https://www.apache.org/licenses/LICENSE-2.0 + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.openrewrite.docker; + +import org.antlr.v4.runtime.*; +import org.intellij.lang.annotations.Language; +import org.jspecify.annotations.Nullable; +import org.openrewrite.ExecutionContext; +import org.openrewrite.InMemoryExecutionContext; +import org.openrewrite.Parser; +import org.openrewrite.SourceFile; +import org.openrewrite.docker.internal.DockerParserVisitor; +import org.openrewrite.docker.internal.grammar.DockerLexer; +import org.openrewrite.docker.tree.Docker; +import org.openrewrite.tree.ParseError; +import org.openrewrite.tree.ParsingEventListener; +import org.openrewrite.tree.ParsingExecutionContextView; + +import java.io.InputStream; +import java.nio.file.Path; +import java.util.regex.Pattern; +import java.util.stream.Stream; + +import static java.lang.String.format; + +public class DockerParser implements Parser { + @Override + public Stream parseInputs(Iterable sourceFiles, @Nullable Path relativeTo, ExecutionContext ctx) { + ParsingEventListener parsingListener = ParsingExecutionContextView.view(ctx).getParsingListener(); + return acceptedInputs(sourceFiles).map(input -> { + parsingListener.startedParsing(input); + try (InputStream sourceStream = input.getSource(ctx)) { + DockerLexer lexer = new DockerLexer(CharStreams.fromStream(sourceStream)); + lexer.removeErrorListeners(); + lexer.addErrorListener(new ForwardingErrorListener(input.getPath(), ctx)); + + org.openrewrite.docker.internal.grammar.DockerParser parser = + new org.openrewrite.docker.internal.grammar.DockerParser(new CommonTokenStream(lexer)); + parser.removeErrorListeners(); + parser.addErrorListener(new ForwardingErrorListener(input.getPath(), ctx)); + + Docker.File file = new DockerParserVisitor( + input.getRelativePath(relativeTo), + input.getFileAttributes(), + input.getSource(ctx) + ).visitDockerfile(parser.dockerfile()); + + parsingListener.parsed(input, file); + return requirePrintEqualsInput(file, input, relativeTo, ctx); + } catch (Throwable t) { + ctx.getOnError().accept(t); + return ParseError.build(this, input, relativeTo, ctx, t); + } + }); + } + + @Override + public Stream parse(@Language("dockerfile") String... sources) { + return parse(new InMemoryExecutionContext(), sources); + } + + private static final Pattern PATTERN = Pattern.compile( + "^dockerfile\\b.*|\\.dockerfile$|^containerfile\\b|\\.containerfile$", + Pattern.CASE_INSENSITIVE); + + @Override + public boolean accept(Path path) { + return PATTERN.matcher(path.getFileName().toString().toLowerCase()).find(); + } + + @Override + public Path sourcePathFromSourceText(Path prefix, String sourceCode) { + return prefix.resolve("Dockerfile"); + } + + private static class ForwardingErrorListener extends BaseErrorListener { + private final Path sourcePath; + private final ExecutionContext ctx; + + private ForwardingErrorListener(Path sourcePath, ExecutionContext ctx) { + this.sourcePath = sourcePath; + this.ctx = ctx; + } + + @Override + public void syntaxError(Recognizer recognizer, Object offendingSymbol, + int line, int charPositionInLine, String msg, RecognitionException e) { + ctx.getOnError().accept(new DockerParsingException(sourcePath, format( + "Syntax error in %s at line %d:%d %s.", + sourcePath, line, charPositionInLine, msg), e)); + } + } + + public static Builder builder() { + return new Builder(); + } + + public static class Builder extends Parser.Builder { + + public Builder() { + super(Docker.File.class); + } + + @Override + public DockerParser build() { + return new DockerParser(); + } + + @Override + public String getDslName() { + return "dockerfile"; + } + } +} diff --git a/rewrite-docker/src/main/java/org/openrewrite/docker/DockerParsingException.java b/rewrite-docker/src/main/java/org/openrewrite/docker/DockerParsingException.java new file mode 100644 index 0000000000..36fe031c68 --- /dev/null +++ b/rewrite-docker/src/main/java/org/openrewrite/docker/DockerParsingException.java @@ -0,0 +1,33 @@ +/* + * Copyright 2026 the original author or authors. + *

+ * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * https://www.apache.org/licenses/LICENSE-2.0 + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.openrewrite.docker; + +import org.jspecify.annotations.Nullable; + +import java.nio.file.Path; + +public class DockerParsingException extends RuntimeException { + private final Path path; + + public DockerParsingException(Path path, String message, @Nullable Throwable cause) { + super(message, cause); + this.path = path; + } + + public Path getPath() { + return path; + } +} diff --git a/rewrite-docker/src/main/java/org/openrewrite/docker/DockerVisitor.java b/rewrite-docker/src/main/java/org/openrewrite/docker/DockerVisitor.java new file mode 100644 index 0000000000..9f052d5d7f --- /dev/null +++ b/rewrite-docker/src/main/java/org/openrewrite/docker/DockerVisitor.java @@ -0,0 +1,303 @@ +/* + * Copyright 2026 the original author or authors. + *

+ * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * https://www.apache.org/licenses/LICENSE-2.0 + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.openrewrite.docker; + +import org.jspecify.annotations.Nullable; +import org.openrewrite.TreeVisitor; +import org.openrewrite.docker.tree.Docker; +import org.openrewrite.docker.tree.Space; +import org.openrewrite.internal.ListUtils; + +public class DockerVisitor

extends TreeVisitor { + + public Space visitSpace(Space space, P p) { + return space; + } + + public Docker visitFile(Docker.File file, P p) { + Docker.File d = file; + d = d.withPrefix(visitSpace(d.getPrefix(), p)); + d = d.withMarkers(visitMarkers(d.getMarkers(), p)); + d = d.withGlobalArgs(ListUtils.map(d.getGlobalArgs(), arg -> (Docker.Arg) visit(arg, p))); + return d.withStages(ListUtils.map(d.getStages(), stage -> (Docker.Stage) visit(stage, p))); + } + + public Docker visitStage(Docker.Stage stage, P p) { + Docker.Stage s = stage; + s = s.withPrefix(visitSpace(s.getPrefix(), p)); + s = s.withMarkers(visitMarkers(s.getMarkers(), p)); + s = s.withFrom((Docker.From) visit(s.getFrom(), p)); + return s.withInstructions(ListUtils.map(s.getInstructions(), inst -> (Docker.Instruction) visit(inst, p))); + } + + public Docker visitFrom(Docker.From from, P p) { + Docker.From f = from; + f = f.withPrefix(visitSpace(f.getPrefix(), p)); + f = f.withMarkers(visitMarkers(f.getMarkers(), p)); + if (f.getFlags() != null) { + f = f.withFlags(ListUtils.map(f.getFlags(), flag -> (Docker.Flag) visit(flag, p))); + } + f = f.withImageName((Docker.Argument) visit(f.getImageName(), p)); + if (f.getTag() != null) { + f = f.withTag((Docker.Argument) visit(f.getTag(), p)); + } + if (f.getDigest() != null) { + f = f.withDigest((Docker.Argument) visit(f.getDigest(), p)); + } + if (f.getAs() != null) { + f = f.withAs(visitFromAs(f.getAs(), p)); + } + return f; + } + + public Docker.From.@Nullable As visitFromAs(Docker.From.As as, P p) { + Docker.From.As a = as; + a = a.withPrefix(visitSpace(a.getPrefix(), p)); + a = a.withMarkers(visitMarkers(a.getMarkers(), p)); + return a.withName((Docker.Literal) visit(a.getName(), p)); + } + + public Docker visitRun(Docker.Run run, P p) { + Docker.Run r = run; + r = r.withPrefix(visitSpace(r.getPrefix(), p)); + r = r.withMarkers(visitMarkers(r.getMarkers(), p)); + if (r.getFlags() != null) { + r = r.withFlags(ListUtils.map(r.getFlags(), flag -> (Docker.Flag) visit(flag, p))); + } + return r.withCommand((Docker.CommandForm) visit(r.getCommand(), p)); + } + + public Docker visitAdd(Docker.Add add, P p) { + Docker.Add a = add; + a = a.withPrefix(visitSpace(a.getPrefix(), p)); + a = a.withMarkers(visitMarkers(a.getMarkers(), p)); + if (a.getFlags() != null) { + a = a.withFlags(ListUtils.map(a.getFlags(), flag -> (Docker.Flag) visit(flag, p))); + } + return a.withForm((Docker.CopyAddForm) visit(a.getForm(), p)); + } + + public Docker visitCopy(Docker.Copy copy, P p) { + Docker.Copy c = copy; + c = c.withPrefix(visitSpace(c.getPrefix(), p)); + c = c.withMarkers(visitMarkers(c.getMarkers(), p)); + if (c.getFlags() != null) { + c = c.withFlags(ListUtils.map(c.getFlags(), flag -> (Docker.Flag) visit(flag, p))); + } + return c.withForm((Docker.CopyAddForm) visit(c.getForm(), p)); + } + + public Docker visitArg(Docker.Arg arg, P p) { + Docker.Arg a = arg; + a = a.withPrefix(visitSpace(a.getPrefix(), p)); + a = a.withMarkers(visitMarkers(a.getMarkers(), p)); + a = a.withName((Docker.Literal) visit(a.getName(), p)); + if (a.getValue() != null) { + a = a.withValue((Docker.Argument) visit(a.getValue(), p)); + } + return a; + } + + public Docker visitEnv(Docker.Env env, P p) { + Docker.Env e = env; + e = e.withPrefix(visitSpace(e.getPrefix(), p)); + e = e.withMarkers(visitMarkers(e.getMarkers(), p)); + return e.withPairs(ListUtils.map(e.getPairs(), pair -> visitEnvPair(pair, p))); + } + + public Docker.Env.EnvPair visitEnvPair(Docker.Env.EnvPair pair, P p) { + Docker.Env.EnvPair ep = pair; + ep = ep.withPrefix(visitSpace(ep.getPrefix(), p)); + ep = ep.withMarkers(visitMarkers(ep.getMarkers(), p)); + ep = ep.withKey((Docker.Literal) visit(ep.getKey(), p)); + return ep.withValue((Docker.Argument) visit(ep.getValue(), p)); + } + + public Docker visitLabel(Docker.Label label, P p) { + Docker.Label l = label; + l = l.withPrefix(visitSpace(l.getPrefix(), p)); + l = l.withMarkers(visitMarkers(l.getMarkers(), p)); + return l.withPairs(ListUtils.map(l.getPairs(), pair -> visitLabelPair(pair, p))); + } + + public Docker.Label.LabelPair visitLabelPair(Docker.Label.LabelPair pair, P p) { + Docker.Label.LabelPair lp = pair; + lp = lp.withPrefix(visitSpace(lp.getPrefix(), p)); + lp = lp.withMarkers(visitMarkers(lp.getMarkers(), p)); + lp = lp.withKey((Docker.Argument) visit(lp.getKey(), p)); + return lp.withValue((Docker.Argument) visit(lp.getValue(), p)); + } + + public Docker visitCmd(Docker.Cmd cmd, P p) { + Docker.Cmd c = cmd; + c = c.withPrefix(visitSpace(c.getPrefix(), p)); + c = c.withMarkers(visitMarkers(c.getMarkers(), p)); + return c.withCommand((Docker.CommandForm) visit(c.getCommand(), p)); + } + + public Docker visitEntrypoint(Docker.Entrypoint entrypoint, P p) { + Docker.Entrypoint e = entrypoint; + e = e.withPrefix(visitSpace(e.getPrefix(), p)); + e = e.withMarkers(visitMarkers(e.getMarkers(), p)); + return e.withCommand((Docker.CommandForm) visit(e.getCommand(), p)); + } + + public Docker visitExpose(Docker.Expose expose, P p) { + Docker.Expose e = expose; + e = e.withPrefix(visitSpace(e.getPrefix(), p)); + e = e.withMarkers(visitMarkers(e.getMarkers(), p)); + return e.withPorts(ListUtils.map(e.getPorts(), port -> (Docker.Port) visit(port, p))); + } + + public Docker visitPort(Docker.Port port, P p) { + Docker.Port pt = port; + pt = pt.withPrefix(visitSpace(pt.getPrefix(), p)); + return pt.withMarkers(visitMarkers(pt.getMarkers(), p)); + } + + public Docker visitVolume(Docker.Volume volume, P p) { + Docker.Volume v = volume; + v = v.withPrefix(visitSpace(v.getPrefix(), p)); + v = v.withMarkers(visitMarkers(v.getMarkers(), p)); + return v.withValues(ListUtils.map(v.getValues(), value -> (Docker.Argument) visit(value, p))); + } + + public Docker visitShell(Docker.Shell shell, P p) { + Docker.Shell s = shell; + s = s.withPrefix(visitSpace(s.getPrefix(), p)); + s = s.withMarkers(visitMarkers(s.getMarkers(), p)); + return s.withArguments(ListUtils.map(s.getArguments(), arg -> (Docker.Argument) visit(arg, p))); + } + + public Docker visitWorkdir(Docker.Workdir workdir, P p) { + Docker.Workdir w = workdir; + w = w.withPrefix(visitSpace(w.getPrefix(), p)); + w = w.withMarkers(visitMarkers(w.getMarkers(), p)); + return w.withPath((Docker.Argument) visit(w.getPath(), p)); + } + + public Docker visitUser(Docker.User user, P p) { + Docker.User u = user; + u = u.withPrefix(visitSpace(u.getPrefix(), p)); + u = u.withMarkers(visitMarkers(u.getMarkers(), p)); + u = u.withUser((Docker.Argument) visit(u.getUser(), p)); + if (u.getGroup() != null) { + u = u.withGroup((Docker.Argument) visit(u.getGroup(), p)); + } + return u; + } + + public Docker visitStopsignal(Docker.Stopsignal stopsignal, P p) { + Docker.Stopsignal s = stopsignal; + s = s.withPrefix(visitSpace(s.getPrefix(), p)); + s = s.withMarkers(visitMarkers(s.getMarkers(), p)); + return s.withSignal((Docker.Argument) visit(s.getSignal(), p)); + } + + public Docker visitOnbuild(Docker.Onbuild onbuild, P p) { + Docker.Onbuild o = onbuild; + o = o.withPrefix(visitSpace(o.getPrefix(), p)); + o = o.withMarkers(visitMarkers(o.getMarkers(), p)); + return o.withInstruction((Docker.Instruction) visit(o.getInstruction(), p)); + } + + public Docker visitHealthcheck(Docker.Healthcheck healthcheck, P p) { + Docker.Healthcheck h = healthcheck; + h = h.withPrefix(visitSpace(h.getPrefix(), p)); + h = h.withMarkers(visitMarkers(h.getMarkers(), p)); + if (h.getFlags() != null) { + h = h.withFlags(ListUtils.map(h.getFlags(), flag -> (Docker.Flag) visit(flag, p))); + } + if (h.getCmd() != null) { + h = h.withCmd((Docker.Cmd) visit(h.getCmd(), p)); + } + return h; + } + + public Docker visitMaintainer(Docker.Maintainer maintainer, P p) { + Docker.Maintainer m = maintainer; + m = m.withPrefix(visitSpace(m.getPrefix(), p)); + m = m.withMarkers(visitMarkers(m.getMarkers(), p)); + return m.withText((Docker.Argument) visit(m.getText(), p)); + } + + public Docker visitShellForm(Docker.ShellForm shellForm, P p) { + Docker.ShellForm sf = shellForm; + sf = sf.withPrefix(visitSpace(sf.getPrefix(), p)); + sf = sf.withMarkers(visitMarkers(sf.getMarkers(), p)); + return sf.withArgument((Docker.Literal) visit(sf.getArgument(), p)); + } + + public Docker visitExecForm(Docker.ExecForm execForm, P p) { + Docker.ExecForm ef = execForm; + ef = ef.withPrefix(visitSpace(ef.getPrefix(), p)); + ef = ef.withMarkers(visitMarkers(ef.getMarkers(), p)); + return ef.withArguments(ListUtils.map(ef.getArguments(), arg -> (Docker.Literal) visit(arg, p))); + } + + public Docker visitHeredocForm(Docker.HeredocForm heredocForm, P p) { + Docker.HeredocForm hf = heredocForm; + hf = hf.withPrefix(visitSpace(hf.getPrefix(), p)); + hf = hf.withMarkers(visitMarkers(hf.getMarkers(), p)); + if (hf.getDestination() != null) { + hf = hf.withDestination((Docker.Argument) visit(hf.getDestination(), p)); + } + return hf.withBodies(ListUtils.map(hf.getBodies(), body -> (Docker.HeredocBody) visit(body, p))); + } + + public Docker visitHeredocBody(Docker.HeredocBody heredocBody, P p) { + Docker.HeredocBody hb = heredocBody; + hb = hb.withPrefix(visitSpace(hb.getPrefix(), p)); + return hb.withMarkers(visitMarkers(hb.getMarkers(), p)); + } + + public Docker visitCopyShellForm(Docker.CopyShellForm copyShellForm, P p) { + Docker.CopyShellForm csf = copyShellForm; + csf = csf.withPrefix(visitSpace(csf.getPrefix(), p)); + csf = csf.withMarkers(visitMarkers(csf.getMarkers(), p)); + csf = csf.withSources(ListUtils.map(csf.getSources(), source -> (Docker.Argument) visit(source, p))); + return csf.withDestination((Docker.Argument) visit(csf.getDestination(), p)); + } + + public Docker visitFlag(Docker.Flag flag, P p) { + Docker.Flag f = flag; + f = f.withPrefix(visitSpace(f.getPrefix(), p)); + f = f.withMarkers(visitMarkers(f.getMarkers(), p)); + if (f.getValue() != null) { + f = f.withValue((Docker.Argument) visit(f.getValue(), p)); + } + return f; + } + + public Docker visitArgument(Docker.Argument argument, P p) { + Docker.Argument a = argument; + a = a.withPrefix(visitSpace(a.getPrefix(), p)); + a = a.withMarkers(visitMarkers(a.getMarkers(), p)); + return a.withContents(ListUtils.map(a.getContents(), content -> (Docker.ArgumentContent) visit(content, p))); + } + + public Docker visitLiteral(Docker.Literal literal, P p) { + Docker.Literal l = literal; + l = l.withPrefix(visitSpace(l.getPrefix(), p)); + return l.withMarkers(visitMarkers(l.getMarkers(), p)); + } + + public Docker visitEnvironmentVariable(Docker.EnvironmentVariable environmentVariable, P p) { + Docker.EnvironmentVariable ev = environmentVariable; + ev = ev.withPrefix(visitSpace(ev.getPrefix(), p)); + return ev.withMarkers(visitMarkers(ev.getMarkers(), p)); + } +} diff --git a/rewrite-docker/src/main/java/org/openrewrite/docker/internal/DockerParserVisitor.java b/rewrite-docker/src/main/java/org/openrewrite/docker/internal/DockerParserVisitor.java new file mode 100644 index 0000000000..43b4becaa9 --- /dev/null +++ b/rewrite-docker/src/main/java/org/openrewrite/docker/internal/DockerParserVisitor.java @@ -0,0 +1,1807 @@ +/* + * Copyright 2026 the original author or authors. + *

+ * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * https://www.apache.org/licenses/LICENSE-2.0 + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.openrewrite.docker.internal; + +import org.antlr.v4.runtime.ParserRuleContext; +import org.antlr.v4.runtime.Token; +import org.antlr.v4.runtime.tree.ParseTree; +import org.antlr.v4.runtime.tree.TerminalNode; +import org.jspecify.annotations.Nullable; +import org.openrewrite.FileAttributes; +import org.openrewrite.docker.internal.grammar.DockerLexer; +import org.openrewrite.docker.internal.grammar.DockerParser; +import org.openrewrite.docker.internal.grammar.DockerParserBaseVisitor; +import org.openrewrite.docker.tree.Docker; +import org.openrewrite.docker.tree.Space; +import org.openrewrite.internal.EncodingDetectingInputStream; +import org.openrewrite.marker.Markers; + +import java.nio.charset.Charset; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; +import java.util.function.BiFunction; + +import static java.util.Collections.emptyList; +import static java.util.Collections.singletonList; +import static java.util.Objects.requireNonNull; +import static org.openrewrite.Tree.randomId; + +public class DockerParserVisitor extends DockerParserBaseVisitor { + private final Path path; + private final String source; + private final Charset charset; + private final boolean charsetBomMarked; + + @Nullable + private final FileAttributes fileAttributes; + + private int cursor = 0; + private int codePointCursor = 0; + + public DockerParserVisitor(Path path, @Nullable FileAttributes fileAttributes, EncodingDetectingInputStream source) { + this.path = path; + this.fileAttributes = fileAttributes; + this.source = source.readFully(); + this.charset = source.getCharset(); + this.charsetBomMarked = source.isCharsetBomMarked(); + } + + @Override + public Docker.File visitDockerfile(DockerParser.DockerfileContext ctx) { + Space prefix = prefix(ctx.getStart()); + + // Parse global ARG instructions (before first FROM) + List globalArgs = new ArrayList<>(); + if (ctx.globalArgs() != null) { + for (DockerParser.ArgInstructionContext argCtx : ctx.globalArgs().argInstruction()) { + Docker.Arg arg = (Docker.Arg) visit(argCtx); + if (arg != null) { + globalArgs.add(arg); + } + } + } + + // Parse build stages + List stages = new ArrayList<>(); + for (DockerParser.StageContext stageCtx : ctx.stage()) { + Docker.Stage stage = visitStage(stageCtx); + if (stage != null) { + stages.add(stage); + } + } + + return new Docker.File( + randomId(), + path, + prefix, + Markers.EMPTY, + charset.name(), + charsetBomMarked, + null, + fileAttributes, + globalArgs, + stages, + Space.format(source, cursor, source.length()) + ); + } + + @Override + public Docker.Stage visitStage(DockerParser.StageContext ctx) { + Space prefix = prefix(ctx.getStart()); + + // Parse the FROM instruction that starts this stage + Docker.From from = (Docker.From) visit(ctx.fromInstruction()); + + // Parse stage instructions + List instructions = new ArrayList<>(); + for (DockerParser.StageInstructionContext instructionCtx : ctx.stageInstruction()) { + Docker.Instruction instruction = (Docker.Instruction) visit(instructionCtx); + if (instruction != null) { + instructions.add(instruction); + } + } + + return new Docker.Stage( + randomId(), + prefix, + Markers.EMPTY, + from, + instructions + ); + } + + @Override + public Docker visitFromInstruction(DockerParser.FromInstructionContext ctx) { + Space prefix = prefix(ctx.getStart()); + + // Extract and skip the FROM keyword + String fromKeyword = ctx.FROM().getText(); + skip(ctx.FROM().getSymbol()); + + List flags = ctx.flags() != null ? convertFlags(ctx.flags()) : null; + Docker.@Nullable Argument[] imageComponents = parseImageName(ctx.imageName()); + Docker.Argument imageName = requireNonNull(imageComponents[0]); + Docker.Argument tag = imageComponents[1]; + Docker.Argument digest = imageComponents[2]; + Docker.From.As as = ctx.AS() != null ? visitFromAs(ctx) : null; + + // Cursor has already been advanced by parseImageName and other parsing methods + // No additional advancement needed here + + return new Docker.From(randomId(), prefix, Markers.EMPTY, fromKeyword, flags, imageName, tag, digest, as); + } + + private Docker.From.As visitFromAs(DockerParser.FromInstructionContext ctx) { + Space asPrefix = prefix(ctx.AS().getSymbol()); + String asKeyword = ctx.AS().getText(); + skip(ctx.AS().getSymbol()); + + // Stage name is always a simple identifier + Space namePrefix = prefix(ctx.stageName().getStart()); + skip(ctx.stageName().getStop()); + Docker.Literal name = new Docker.Literal( + randomId(), + namePrefix, + Markers.EMPTY, + ctx.stageName().getText(), + null + ); + + return new Docker.From.As( + randomId(), + asPrefix, + Markers.EMPTY, + asKeyword, + name + ); + } + + private Docker.@Nullable Argument[] parseImageName(DockerParser.ImageNameContext ctx) { + Space prefix = prefix(ctx); + + // Parse the text and split out environment variables + List contents = parseText(ctx.text()); + + // Advance cursor to end of text + advanceCursor(ctx.text().getStop().getStopIndex() + 1); + + // If the entire image is a single quoted string, don't split it + if (contents.size() == 1 && contents.get(0) instanceof Docker.Literal && ((Docker.Literal) contents.get(0)).isQuoted()) { + // Single quoted string - keep it as-is + Docker.Argument imageName = new Docker.Argument(randomId(), prefix, Markers.EMPTY, contents); + return new Docker.@Nullable Argument[]{imageName, null, null}; + } + + // Split contents into imageName, tag, and digest components + List imageNameContents = new ArrayList<>(); + List tagContents = new ArrayList<>(); + List digestContents = new ArrayList<>(); + + boolean foundColon = false; + boolean foundAt = false; + + for (Docker.ArgumentContent content : contents) { + if (content instanceof Docker.Literal && !((Docker.Literal) content).isQuoted()) { + String text = ((Docker.Literal) content).getText(); + + // Look for @ first (digest takes precedence over tag) + int atIndex = text.indexOf('@'); + int colonIndex = text.indexOf(':'); + + if (atIndex >= 0 && !foundAt) { + // Split at @ + foundAt = true; + String imagePart = text.substring(0, atIndex); + String digestPart = text.substring(atIndex + 1); + + if (!imagePart.isEmpty()) { + imageNameContents.add(new Docker.Literal(randomId(), Space.EMPTY, Markers.EMPTY, imagePart, null)); + } + if (!digestPart.isEmpty()) { + digestContents.add(new Docker.Literal(randomId(), Space.EMPTY, Markers.EMPTY, digestPart, null)); + } + } else if (colonIndex >= 0 && !foundColon && !foundAt) { + // Split at : + foundColon = true; + String imagePart = text.substring(0, colonIndex); + String tagPart = text.substring(colonIndex + 1); + + if (!imagePart.isEmpty()) { + imageNameContents.add(new Docker.Literal(randomId(), Space.EMPTY, Markers.EMPTY, imagePart, null)); + } + if (!tagPart.isEmpty()) { + tagContents.add(new Docker.Literal(randomId(), Space.EMPTY, Markers.EMPTY, tagPart, null)); + } + } else { + // Add to appropriate list + if (foundAt) { + digestContents.add(content); + } else if (foundColon) { + tagContents.add(content); + } else { + imageNameContents.add(content); + } + } + } else { + // Environment variables or quoted strings + if (foundAt) { + digestContents.add(content); + } else if (foundColon) { + tagContents.add(content); + } else { + imageNameContents.add(content); + } + } + } + + Docker.Argument imageName = new Docker.Argument(randomId(), prefix, Markers.EMPTY, imageNameContents); + Docker.Argument tag = tagContents.isEmpty() ? null : + new Docker.Argument(randomId(), Space.EMPTY, Markers.EMPTY, tagContents); + Docker.Argument digest = digestContents.isEmpty() ? null : + new Docker.Argument(randomId(), Space.EMPTY, Markers.EMPTY, digestContents); + + return new Docker.@Nullable Argument[]{imageName, tag, digest}; + } + + private List parseText(DockerParser.@Nullable TextContext textCtx) { + List contents = new ArrayList<>(); + + if (textCtx == null) { + return contents; + } + + // Get the actual text from source (including HIDDEN channel whitespace) + int startIndex = textCtx.getStart().getStartIndex(); + int stopIndex = textCtx.getStop().getStopIndex(); + + // Defensive check: ensure stopIndex >= startIndex + if (stopIndex < startIndex) { + // This can happen with certain edge cases; return empty contents + return contents; + } + + int startCharIndex = source.offsetByCodePoints(0, startIndex); + int stopCharIndex = source.offsetByCodePoints(0, stopIndex + 1); + + // Another defensive check after offset calculation + if (stopCharIndex < startCharIndex) { + return contents; + } + + String fullText = source.substring(startCharIndex, stopCharIndex); + + boolean hasQuotedString = fullText.contains("\"") || fullText.contains("'"); + boolean hasEnvironmentVariable = fullText.contains("$"); + boolean hasComment = fullText.contains("#"); + + if (!hasQuotedString && !hasEnvironmentVariable && !hasComment) { + // Simple case: just plain text + contents.add(new Docker.Literal( + randomId(), + Space.EMPTY, + Markers.EMPTY, + fullText, + null + )); + return contents; + } + + // Complex case: parse token by token + boolean foundComment = false; + for (int i = textCtx.getChildCount() - 1; i >= 0; i--) { + ParseTree child = textCtx.getChild(i); + if (child instanceof DockerParser.TextElementContext) { + DockerParser.TextElementContext textElement = (DockerParser.TextElementContext) child; + if (textElement.getChildCount() > 0 && textElement.getChild(0) instanceof TerminalNode) { + TerminalNode terminal = (TerminalNode) textElement.getChild(0); + if (terminal.getSymbol().getType() == DockerLexer.COMMENT) { + foundComment = true; + break; + } + } + } + } + + for (int i = 0; i < textCtx.getChildCount(); i++) { + ParseTree child = textCtx.getChild(i); + + if (child instanceof DockerParser.TextElementContext) { + DockerParser.TextElementContext textElement = (DockerParser.TextElementContext) child; + if (textElement.getChildCount() > 0 && textElement.getChild(0) instanceof TerminalNode) { + TerminalNode terminal = (TerminalNode) textElement.getChild(0); + Token token = terminal.getSymbol(); + String tokenText = token.getText(); + + if (token.getType() == DockerLexer.COMMENT) { + // COMMENT tokens are ignored - they will be part of next element's prefix + break; // Stop processing tokens once we hit a comment + } else if (token.getType() == DockerLexer.DOUBLE_QUOTED_STRING) { + Space elementPrefix = prefix(token); + skip(token); + String value = tokenText.substring(1, tokenText.length() - 1); + contents.add(new Docker.Literal( + randomId(), + elementPrefix, + Markers.EMPTY, + value, + Docker.Literal.QuoteStyle.DOUBLE + )); + } else if (token.getType() == DockerLexer.SINGLE_QUOTED_STRING) { + Space elementPrefix = prefix(token); + skip(token); + String value = tokenText.substring(1, tokenText.length() - 1); + contents.add(new Docker.Literal( + randomId(), + elementPrefix, + Markers.EMPTY, + value, + Docker.Literal.QuoteStyle.SINGLE + )); + } else if (token.getType() == DockerLexer.ENV_VAR) { + Space elementPrefix = prefix(token); + skip(token); + boolean braced = tokenText.startsWith("${"); + String varName = braced ? tokenText.substring(2, tokenText.length() - 1) : tokenText.substring(1); + contents.add(new Docker.EnvironmentVariable( + randomId(), + elementPrefix, + Markers.EMPTY, + varName, + braced + )); + } else { + // Plain text for other tokens + Space elementPrefix = prefix(token); + skip(token); + contents.add(new Docker.Literal( + randomId(), + elementPrefix, + Markers.EMPTY, + tokenText, + null + )); + } + } + } + } + + return contents; + } + + @Override + public Docker visitRunInstruction(DockerParser.RunInstructionContext ctx) { + Space prefix = prefix(ctx.getStart()); + + // Extract and skip the RUN keyword + String runKeyword = ctx.RUN().getText(); + skip(ctx.RUN().getSymbol()); + + List flags = ctx.flags() != null ? convertFlags(ctx.flags()) : null; + Docker.CommandForm command = visitCommandFormForRun(ctx); + + // Advance cursor to end of instruction + if (ctx.getStop() != null) { + advanceCursor(ctx.getStop().getStopIndex() + 1); + } + + return new Docker.Run(randomId(), prefix, Markers.EMPTY, runKeyword, flags, command); + } + + @Override + public Docker visitAddInstruction(DockerParser.AddInstructionContext ctx) { + Space prefix = prefix(ctx.getStart()); + + // Extract and skip the ADD keyword + String addKeyword = ctx.ADD().getText(); + skip(ctx.ADD().getSymbol()); + + List flags = ctx.flags() != null ? convertFlags(ctx.flags()) : null; + + Docker.CopyAddForm form; + + // Check if heredoc, jsonArray, or sourceList is present + if (ctx.heredoc() != null) { + // For ADD with heredoc, extract destination from preamble + form = visitHeredocContext(ctx.heredoc(), true); + } else if (ctx.jsonArray() != null) { + form = visitJsonArrayAsExecForm(ctx.jsonArray()); + } else if (ctx.sourceList() != null) { + // Parse sources and destination into CopyShellForm + List sources = parseSourcePaths(ctx.sourceList()); + Docker.Argument destination = parseDestinationPath(ctx.destination()); + // The prefix for shellForm comes from the first source + Space shellFormPrefix = sources.isEmpty() ? Space.EMPTY : sources.get(0).getPrefix(); + if (!sources.isEmpty()) { + // Remove prefix from first source since it's now on the shellForm + sources.set(0, sources.get(0).withPrefix(Space.EMPTY)); + } + form = new Docker.CopyShellForm(randomId(), shellFormPrefix, Markers.EMPTY, sources, destination); + } else { + throw new IllegalStateException("ADD must have either sourceList or jsonArray or heredoc"); + } + + // Advance cursor to end of instruction + if (ctx.getStop() != null) { + advanceCursor(ctx.getStop().getStopIndex() + 1); + } + + return new Docker.Add(randomId(), prefix, Markers.EMPTY, addKeyword, flags, form); + } + + @Override + public Docker visitCopyInstruction(DockerParser.CopyInstructionContext ctx) { + Space prefix = prefix(ctx.getStart()); + + // Extract and skip the COPY keyword + String copyKeyword = ctx.COPY().getText(); + skip(ctx.COPY().getSymbol()); + + List flags = ctx.flags() != null ? convertFlags(ctx.flags()) : null; + + Docker.CopyAddForm form; + + // Check if heredoc, jsonArray, or sourceList is present + if (ctx.heredoc() != null) { + // For COPY with heredoc, extract destination from preamble + form = visitHeredocContext(ctx.heredoc(), true); + } else if (ctx.jsonArray() != null) { + form = visitJsonArrayAsExecForm(ctx.jsonArray()); + } else if (ctx.sourceList() != null) { + // Parse sources and destination into CopyShellForm + List sources = parseSourcePaths(ctx.sourceList()); + Docker.Argument destination = parseDestinationPath(ctx.destination()); + // The prefix for shellForm comes from the first source + Space shellFormPrefix = sources.isEmpty() ? Space.EMPTY : sources.get(0).getPrefix(); + if (!sources.isEmpty()) { + // Remove prefix from first source since it's now on the shellForm + sources.set(0, sources.get(0).withPrefix(Space.EMPTY)); + } + form = new Docker.CopyShellForm(randomId(), shellFormPrefix, Markers.EMPTY, sources, destination); + } else { + throw new IllegalStateException("COPY must have either sourceList or jsonArray or heredoc"); + } + + // Advance cursor to end of instruction + if (ctx.getStop() != null) { + advanceCursor(ctx.getStop().getStopIndex() + 1); + } + + return new Docker.Copy(randomId(), prefix, Markers.EMPTY, copyKeyword, flags, form); + } + + /** + * Parse source paths from the grammar's sourceList context. + * With lexer modes for flag values, the grammar's token allocation is now correct. + */ + private List parseSourcePaths(DockerParser.SourceListContext ctx) { + List sources = new ArrayList<>(); + for (DockerParser.SourcePathContext pathCtx : ctx.sourcePath()) { + sources.add(parsePathToken(pathCtx)); + } + return sources; + } + + /** + * Parse destination path from the grammar's destination context. + */ + private Docker.Argument parseDestinationPath(DockerParser.DestinationContext ctx) { + return parsePathToken(ctx.destinationPath()); + } + + /** + * Parse a single path token (source or destination) into an Argument. + */ + private Docker.Argument parsePathToken(ParserRuleContext pathCtx) { + Space argPrefix = prefix(pathCtx.getStart()); + Token token = pathCtx.getStart(); + String tokenText = token.getText(); + skip(token); + + Docker.ArgumentContent content; + if (token.getType() == DockerLexer.DOUBLE_QUOTED_STRING) { + String value = tokenText.substring(1, tokenText.length() - 1); + content = new Docker.Literal(randomId(), Space.EMPTY, Markers.EMPTY, value, Docker.Literal.QuoteStyle.DOUBLE); + } else if (token.getType() == DockerLexer.SINGLE_QUOTED_STRING) { + String value = tokenText.substring(1, tokenText.length() - 1); + content = new Docker.Literal(randomId(), Space.EMPTY, Markers.EMPTY, value, Docker.Literal.QuoteStyle.SINGLE); + } else if (token.getType() == DockerLexer.ENV_VAR) { + boolean braced = tokenText.startsWith("${"); + String varName = braced ? tokenText.substring(2, tokenText.length() - 1) : tokenText.substring(1); + content = new Docker.EnvironmentVariable(randomId(), Space.EMPTY, Markers.EMPTY, varName, braced); + } else { + // UNQUOTED_TEXT - store as plain literal (includes complex paths like ${VAR}/path) + content = new Docker.Literal(randomId(), Space.EMPTY, Markers.EMPTY, tokenText, null); + } + + return new Docker.Argument(randomId(), argPrefix, Markers.EMPTY, singletonList(content)); + } + + @Override + public Docker visitArgInstruction(DockerParser.ArgInstructionContext ctx) { + Space prefix = prefix(ctx.getStart()); + + // Extract and skip the ARG keyword + String argKeyword = ctx.ARG().getText(); + skip(ctx.ARG().getSymbol()); + + // ARG name is always a simple identifier + Space namePrefix = prefix(ctx.argName().getStart()); + skip(ctx.argName().getStop()); + Docker.Literal name = new Docker.Literal( + randomId(), + namePrefix, + Markers.EMPTY, + ctx.argName().getText(), + null + ); + + Docker.Argument value = null; + if (ctx.EQUALS() != null) { + skip(ctx.EQUALS().getSymbol()); + value = visitArgument(ctx.argValue()); + } + + // Advance cursor to end of instruction + if (ctx.getStop() != null) { + advanceCursor(ctx.getStop().getStopIndex() + 1); + } + + return new Docker.Arg(randomId(), prefix, Markers.EMPTY, argKeyword, name, value); + } + + @Override + public Docker visitEnvInstruction(DockerParser.EnvInstructionContext ctx) { + Space prefix = prefix(ctx.getStart()); + + // Extract and skip the ENV keyword + String envKeyword = ctx.ENV().getText(); + skip(ctx.ENV().getSymbol()); + + // Parse env pairs + List pairs = new ArrayList<>(); + for (DockerParser.EnvPairContext pairCtx : ctx.envPairs().envPair()) { + Space pairPrefix = prefix(pairCtx.getStart()); + + // ENV key is always a simple identifier + Space keyPrefix = prefix(pairCtx.envKey().getStart()); + skip(pairCtx.envKey().getStop()); + Docker.Literal key = new Docker.Literal( + randomId(), + keyPrefix, + Markers.EMPTY, + pairCtx.envKey().getText(), + null + ); + + boolean hasEquals = pairCtx.EQUALS() != null; + if (hasEquals) { + skip(pairCtx.EQUALS().getSymbol()); + } + + // Handle both forms: KEY=value (envValueEquals) or KEY value (envValueSpace) + Docker.Argument value; + if (pairCtx.envValueEquals() != null) { + value = visitArgument(pairCtx.envValueEquals().envTextEquals()); + } else { + value = visitArgument(pairCtx.envValueSpace()); + } + + pairs.add(new Docker.Env.EnvPair(randomId(), pairPrefix, Markers.EMPTY, key, hasEquals, value)); + } + + // Advance cursor to end of instruction + if (ctx.getStop() != null) { + advanceCursor(ctx.getStop().getStopIndex() + 1); + } + + return new Docker.Env(randomId(), prefix, Markers.EMPTY, envKeyword, pairs); + } + + @Override + public Docker visitLabelInstruction(DockerParser.LabelInstructionContext ctx) { + Space prefix = prefix(ctx.getStart()); + + // Extract and skip the LABEL keyword + String labelKeyword = ctx.LABEL().getText(); + skip(ctx.LABEL().getSymbol()); + + // Parse label pairs + List pairs = new ArrayList<>(); + for (DockerParser.LabelPairContext pairCtx : ctx.labelPairs().labelPair()) { + Space pairPrefix = prefix(pairCtx.getStart()); + boolean hasEquals = pairCtx.EQUALS() != null; + Docker.Argument key; + Docker.Argument value; + + if (hasEquals) { + // New format: LABEL key=value + key = visitLabelKeyOrValue(pairCtx.labelKey()); + skip(pairCtx.EQUALS().getSymbol()); + value = visitLabelKeyOrValue(pairCtx.labelValue()); + } else { + // Old format: LABEL key value + key = visitLabelKeyOrValue(pairCtx.labelKey()); + value = parseLabelOldValue(pairCtx.labelOldValue()); + } + + pairs.add(new Docker.Label.LabelPair(randomId(), pairPrefix, Markers.EMPTY, key, hasEquals, value)); + } + + // Advance cursor to end of instruction + if (ctx.getStop() != null) { + advanceCursor(ctx.getStop().getStopIndex() + 1); + } + + return new Docker.Label(randomId(), prefix, Markers.EMPTY, labelKeyword, pairs); + } + + private Docker.Argument visitLabelKeyOrValue(ParserRuleContext ctx) { + // labelKey and labelValue can be UNQUOTED_TEXT, DOUBLE_QUOTED_STRING, or SINGLE_QUOTED_STRING + Space prefix = prefix(ctx.getStart()); + List contents = new ArrayList<>(); + + if (ctx.getChildCount() > 0) { + ParseTree child = ctx.getChild(0); + if (child instanceof TerminalNode) { + TerminalNode terminal = (TerminalNode) child; + Token token = terminal.getSymbol(); + String text = token.getText(); + + if (token.getType() == DockerLexer.DOUBLE_QUOTED_STRING) { + // Remove quotes + String value = text.substring(1, text.length() - 1); + contents.add(new Docker.Literal(randomId(), Space.EMPTY, Markers.EMPTY, value, Docker.Literal.QuoteStyle.DOUBLE)); + skip(token); + } else if (token.getType() == DockerLexer.SINGLE_QUOTED_STRING) { + // Remove quotes + String value = text.substring(1, text.length() - 1); + contents.add(new Docker.Literal(randomId(), Space.EMPTY, Markers.EMPTY, value, Docker.Literal.QuoteStyle.SINGLE)); + skip(token); + } else { + // UNQUOTED_TEXT + contents.add(new Docker.Literal(randomId(), Space.EMPTY, Markers.EMPTY, text, null)); + skip(token); + } + } + } + + return new Docker.Argument(randomId(), prefix, Markers.EMPTY, contents); + } + + private Docker.Argument parseLabelOldValue(DockerParser.LabelOldValueContext ctx) { + Space prefix = prefix(ctx.getStart()); + List contents = new ArrayList<>(); + + for (DockerParser.LabelOldValueElementContext elemCtx : ctx.labelOldValueElement()) { + if (elemCtx.getChildCount() > 0) { + ParseTree child = elemCtx.getChild(0); + if (child instanceof TerminalNode) { + TerminalNode terminal = (TerminalNode) child; + Token token = terminal.getSymbol(); + String text = token.getText(); + Space elementPrefix = prefix(token); + skip(token); + + if (token.getType() == DockerLexer.DOUBLE_QUOTED_STRING) { + String value = text.substring(1, text.length() - 1); + contents.add(new Docker.Literal(randomId(), elementPrefix, Markers.EMPTY, value, Docker.Literal.QuoteStyle.DOUBLE)); + } else if (token.getType() == DockerLexer.SINGLE_QUOTED_STRING) { + String value = text.substring(1, text.length() - 1); + contents.add(new Docker.Literal(randomId(), elementPrefix, Markers.EMPTY, value, Docker.Literal.QuoteStyle.SINGLE)); + } else if (token.getType() == DockerLexer.ENV_VAR) { + boolean braced = text.startsWith("${"); + String varName = braced ? + text.substring(2, text.indexOf('}')) : + text.substring(1); + contents.add(new Docker.EnvironmentVariable(randomId(), elementPrefix, Markers.EMPTY, varName, braced)); + } else { + // Plain text - includes UNQUOTED_TEXT, EQUALS, DASH_DASH, and instruction keywords + contents.add(new Docker.Literal(randomId(), elementPrefix, Markers.EMPTY, text, null)); + } + } + } + } + + advanceCursor(ctx.getStop().getStopIndex() + 1); + return new Docker.Argument(randomId(), prefix, Markers.EMPTY, contents); + } + + @Override + public Docker visitCmdInstruction(DockerParser.CmdInstructionContext ctx) { + Space prefix = prefix(ctx.getStart()); + + String cmdKeyword = ctx.CMD().getText(); + skip(ctx.CMD().getSymbol()); + + Docker.CommandForm command = visitCommandFormForCmd(ctx); + + // Advance cursor to end of instruction + if (ctx.getStop() != null) { + advanceCursor(ctx.getStop().getStopIndex() + 1); + } + + return new Docker.Cmd(randomId(), prefix, Markers.EMPTY, cmdKeyword, command); + } + + @Override + public Docker visitEntrypointInstruction(DockerParser.EntrypointInstructionContext ctx) { + Space prefix = prefix(ctx.getStart()); + + String entrypointKeyword = ctx.ENTRYPOINT().getText(); + skip(ctx.ENTRYPOINT().getSymbol()); + + Docker.CommandForm command = visitCommandFormForEntrypoint(ctx); + + // Advance cursor to end of instruction + if (ctx.getStop() != null) { + advanceCursor(ctx.getStop().getStopIndex() + 1); + } + + return new Docker.Entrypoint(randomId(), prefix, Markers.EMPTY, entrypointKeyword, command); + } + + @Override + public Docker visitExposeInstruction(DockerParser.ExposeInstructionContext ctx) { + Space prefix = prefix(ctx.getStart()); + + // Extract and skip the EXPOSE keyword + String exposeKeyword = ctx.EXPOSE().getText(); + skip(ctx.EXPOSE().getSymbol()); + + // Parse port list + List ports = new ArrayList<>(); + for (DockerParser.PortContext portCtx : ctx.portList().port()) { + ports.add(convertPort(portCtx)); + } + + // Advance cursor to end of instruction + if (ctx.getStop() != null) { + advanceCursor(ctx.getStop().getStopIndex() + 1); + } + + return new Docker.Expose(randomId(), prefix, Markers.EMPTY, exposeKeyword, ports); + } + + private Docker.Port convertPort(DockerParser.PortContext ctx) { + Space prefix = prefix(ctx.getStart()); + String text; + + if (ctx.UNQUOTED_TEXT() != null) { + Token token = ctx.UNQUOTED_TEXT().getSymbol(); + text = token.getText(); + skip(token); + } else if (ctx.ENV_VAR() != null) { + Token token = ctx.ENV_VAR().getSymbol(); + text = token.getText(); + skip(token); + } else { + // Handle other token types (COMMAND_SUBST, BACKTICK_SUBST, SPECIAL_VAR, AS) + text = ctx.getText(); + if (ctx.getStop() != null) { + advanceCursor(ctx.getStop().getStopIndex() + 1); + } + } + + return parsePort(prefix, text); + } + + /** + * Parses a port specification into a structured Port object. + * Supports formats: 80, 80/tcp, 80/udp, 8000-9000, 8000-9000/tcp, ${PORT} + */ + private Docker.Port parsePort(Space prefix, String text) { + Integer start = null; + Integer end = null; + Docker.Port.Protocol protocol = Docker.Port.Protocol.TCP; + + String portPart = text; + + // Check for protocol suffix + int slashIndex = text.lastIndexOf('/'); + if (slashIndex > 0) { + String protocolStr = text.substring(slashIndex + 1).toLowerCase(); + if ("udp".equals(protocolStr)) { + protocol = Docker.Port.Protocol.UDP; + } + portPart = text.substring(0, slashIndex); + } + + // Check for range (e.g., 8000-9000) + int dashIndex = portPart.indexOf('-'); + if (dashIndex > 0) { + try { + start = Integer.parseInt(portPart.substring(0, dashIndex)); + end = Integer.parseInt(portPart.substring(dashIndex + 1)); + } catch (NumberFormatException e) { + // Contains variable or unparseable - leave start/end null + } + } else { + // Single port + try { + start = Integer.parseInt(portPart); + } catch (NumberFormatException e) { + // Contains variable or unparseable - leave start null + } + } + + return new Docker.Port(randomId(), prefix, Markers.EMPTY, text, start, end, protocol); + } + + @Override + public Docker visitVolumeInstruction(DockerParser.VolumeInstructionContext ctx) { + Space prefix = prefix(ctx.getStart()); + + // Extract and skip the VOLUME keyword + String volumeKeyword = ctx.VOLUME().getText(); + skip(ctx.VOLUME().getSymbol()); + + boolean jsonForm = ctx.jsonArray() != null; + List values = new ArrayList<>(); + Space openingBracketPrefix = Space.EMPTY; + Space closingBracketPrefix = Space.EMPTY; + + if (jsonForm && ctx.jsonArray() != null && ctx.jsonArray().LBRACKET() != null) { + // Capture the whitespace before the opening bracket + openingBracketPrefix = prefix(ctx.jsonArray().LBRACKET().getSymbol()); + // Parse JSON array + JsonArrayParseResult result = visitJsonArrayForVolume(ctx.jsonArray()); + values = result.arguments; + closingBracketPrefix = result.closingBracketPrefix; + } else if (ctx.pathList() != null) { + // Parse path list (space-separated paths) + for (DockerParser.VolumePathContext pathCtx : ctx.pathList().volumePath()) { + Space pathPrefix = prefix(pathCtx.getStart()); + Token token; + String text; + + if (pathCtx.UNQUOTED_TEXT() != null) { + token = pathCtx.UNQUOTED_TEXT().getSymbol(); + text = token.getText(); + skip(token); + List contents = new ArrayList<>(); + contents.add(new Docker.Literal(randomId(), Space.EMPTY, Markers.EMPTY, text, null)); + values.add(new Docker.Argument(randomId(), pathPrefix, Markers.EMPTY, contents)); + } else if (pathCtx.DOUBLE_QUOTED_STRING() != null) { + token = pathCtx.DOUBLE_QUOTED_STRING().getSymbol(); + text = token.getText(); + skip(token); + List contents = new ArrayList<>(); + contents.add(new Docker.Literal(randomId(), Space.EMPTY, Markers.EMPTY, + text.substring(1, text.length() - 1), Docker.Literal.QuoteStyle.DOUBLE)); + values.add(new Docker.Argument(randomId(), pathPrefix, Markers.EMPTY, contents)); + } else if (pathCtx.SINGLE_QUOTED_STRING() != null) { + token = pathCtx.SINGLE_QUOTED_STRING().getSymbol(); + text = token.getText(); + skip(token); + List contents = new ArrayList<>(); + contents.add(new Docker.Literal(randomId(), Space.EMPTY, Markers.EMPTY, + text.substring(1, text.length() - 1), Docker.Literal.QuoteStyle.SINGLE)); + values.add(new Docker.Argument(randomId(), pathPrefix, Markers.EMPTY, contents)); + } else if (pathCtx.ENV_VAR() != null) { + token = pathCtx.ENV_VAR().getSymbol(); + text = token.getText(); + skip(token); + List contents = new ArrayList<>(); + contents.add(createEnvVar(text)); + values.add(new Docker.Argument(randomId(), pathPrefix, Markers.EMPTY, contents)); + } + } + } + + // Advance cursor to end of instruction + if (ctx.getStop() != null) { + advanceCursor(ctx.getStop().getStopIndex() + 1); + } + + return new Docker.Volume(randomId(), prefix, Markers.EMPTY, volumeKeyword, jsonForm, openingBracketPrefix, values, closingBracketPrefix); + } + + private JsonArrayParseResult visitJsonArrayForVolume(DockerParser.JsonArrayContext ctx) { + // Skip the opening bracket - the space after VOLUME is handled by instruction prefix + skip(ctx.LBRACKET().getSymbol()); + + List arguments = new ArrayList<>(); + if (ctx.jsonArrayElements() != null) { + DockerParser.JsonArrayElementsContext elementsCtx = ctx.jsonArrayElements(); + List jsonStrings = elementsCtx.jsonString(); + + for (int i = 0; i < jsonStrings.size(); i++) { + // convertJsonString captures the prefix correctly (space after [ or after ,) + Docker.Argument arg = convertJsonString(jsonStrings.get(i)); + arguments.add(arg); + + // Skip comma after this element if it's not the last one + // The grammar is: jsonString ( COMMA jsonString )* + // So we need to skip the COMMA tokens + if (i < jsonStrings.size() - 1) { + // Find and skip the COMMA token between this element and the next + for (int j = 0; j < elementsCtx.getChildCount(); j++) { + if (elementsCtx.getChild(j) instanceof TerminalNode) { + TerminalNode terminal = (TerminalNode) elementsCtx.getChild(j); + if (terminal.getSymbol().getType() == DockerLexer.COMMA && + terminal.getSymbol().getStartIndex() > jsonStrings.get(i).getStop().getStopIndex() && + terminal.getSymbol().getStartIndex() < jsonStrings.get(i + 1).getStart().getStartIndex()) { + skip(terminal.getSymbol()); + break; + } + } + } + } + } + } + + // Capture whitespace before closing bracket to preserve " ]" vs "]" + Space closingBracketPrefix = prefix(ctx.RBRACKET().getSymbol()); + skip(ctx.RBRACKET().getSymbol()); + + return new JsonArrayParseResult(arguments, closingBracketPrefix); + } + + private Docker.Argument convertJsonString(DockerParser.JsonStringContext ctx) { + Space prefix = prefix(ctx.getStart()); + Token token = ctx.DOUBLE_QUOTED_STRING().getSymbol(); + String text = token.getText(); + + // Remove quotes + String value = text.substring(1, text.length() - 1); + skip(token); + + // Also need to skip any COMMA token that follows (if this is not the last element) + // The COMMA is part of jsonArrayElements, so we need to handle it there + // Actually, we'll handle commas in the calling method + + List contents = new ArrayList<>(); + contents.add(new Docker.Literal(randomId(), Space.EMPTY, Markers.EMPTY, value, Docker.Literal.QuoteStyle.DOUBLE)); + return new Docker.Argument(randomId(), prefix, Markers.EMPTY, contents); + } + + @Override + public Docker visitShellInstruction(DockerParser.ShellInstructionContext ctx) { + Space prefix = prefix(ctx.getStart()); + + // Extract and skip the SHELL keyword + String shellKeyword = ctx.SHELL().getText(); + skip(ctx.SHELL().getSymbol()); + + // Capture the whitespace before the opening bracket + Space openingBracketPrefix = Space.EMPTY; + + // Parse JSON array (may be null or malformed in some edge cases) + JsonArrayParseResult result; + if (ctx.jsonArray() != null && ctx.jsonArray().LBRACKET() != null) { + openingBracketPrefix = prefix(ctx.jsonArray().LBRACKET().getSymbol()); + result = visitJsonArrayForShell(ctx.jsonArray()); + } else { + result = new JsonArrayParseResult(emptyList(), Space.EMPTY); + } + + // Advance cursor to end of instruction + if (ctx.getStop() != null) { + advanceCursor(ctx.getStop().getStopIndex() + 1); + } + + return new Docker.Shell(randomId(), prefix, Markers.EMPTY, shellKeyword, openingBracketPrefix, result.arguments, result.closingBracketPrefix); + } + + private JsonArrayParseResult visitJsonArrayForShell(DockerParser.JsonArrayContext ctx) { + // Skip the opening bracket - the space after SHELL is handled by instruction prefix + skip(ctx.LBRACKET().getSymbol()); + + List arguments = new ArrayList<>(); + if (ctx.jsonArrayElements() != null) { + DockerParser.JsonArrayElementsContext elementsCtx = ctx.jsonArrayElements(); + List jsonStrings = elementsCtx.jsonString(); + + for (int i = 0; i < jsonStrings.size(); i++) { + // convertJsonString captures the prefix correctly (space after [ or after ,) + Docker.Argument arg = convertJsonString(jsonStrings.get(i)); + arguments.add(arg); + + // Skip comma after this element if it's not the last one + if (i < jsonStrings.size() - 1) { + // Find and skip the COMMA token between this element and the next + for (int j = 0; j < elementsCtx.getChildCount(); j++) { + if (elementsCtx.getChild(j) instanceof TerminalNode) { + TerminalNode terminal = (TerminalNode) elementsCtx.getChild(j); + if (terminal.getSymbol().getType() == DockerLexer.COMMA && + terminal.getSymbol().getStartIndex() > jsonStrings.get(i).getStop().getStopIndex() && + terminal.getSymbol().getStartIndex() < jsonStrings.get(i + 1).getStart().getStartIndex()) { + skip(terminal.getSymbol()); + break; + } + } + } + } + } + } + + // Capture whitespace before closing bracket to preserve " ]" vs "]" + Space closingBracketPrefix = prefix(ctx.RBRACKET().getSymbol()); + skip(ctx.RBRACKET().getSymbol()); + + return new JsonArrayParseResult(arguments, closingBracketPrefix); + } + + @Override + public Docker visitWorkdirInstruction(DockerParser.WorkdirInstructionContext ctx) { + Space prefix = prefix(ctx.getStart()); + + String workdirKeyword = ctx.WORKDIR().getText(); + skip(ctx.WORKDIR().getSymbol()); + + Docker.Argument path = visitArgument(ctx.path()); + + // Advance cursor to end of instruction + if (ctx.getStop() != null) { + advanceCursor(ctx.getStop().getStopIndex() + 1); + } + + return new Docker.Workdir(randomId(), prefix, Markers.EMPTY, workdirKeyword, path); + } + + @Override + public Docker visitUserInstruction(DockerParser.UserInstructionContext ctx) { + Space prefix = prefix(ctx.getStart()); + + String userKeyword = ctx.USER().getText(); + skip(ctx.USER().getSymbol()); + + // Parse userSpec and split into user and optional group + Docker.@Nullable Argument[] userAndGroup = parseUserSpec(ctx.userSpec()); + Docker.Argument user = requireNonNull(userAndGroup[0]); + Docker.Argument group = userAndGroup[1]; + + // Advance cursor to end of instruction + if (ctx.getStop() != null) { + advanceCursor(ctx.getStop().getStopIndex() + 1); + } + + return new Docker.User(randomId(), prefix, Markers.EMPTY, userKeyword, user, group); + } + + private Docker.@Nullable Argument[] parseUserSpec(DockerParser.UserSpecContext ctx) { + Space prefix = prefix(ctx); + + // Parse the text + List contents = parseText(ctx.text()); + + // Advance cursor to end of text + advanceCursor(ctx.text().getStop().getStopIndex() + 1); + + // Find the colon separator to split user and group + List userContents = new ArrayList<>(); + List groupContents = new ArrayList<>(); + boolean foundColon = false; + + for (Docker.ArgumentContent content : contents) { + if (content instanceof Docker.Literal && !((Docker.Literal) content).isQuoted()) { + String text = ((Docker.Literal) content).getText(); + int colonIndex = text.indexOf(':'); + + if (colonIndex >= 0 && !foundColon) { + // Split at the colon + foundColon = true; + String userPart = text.substring(0, colonIndex); + String groupPart = text.substring(colonIndex + 1); + + if (!userPart.isEmpty()) { + userContents.add(new Docker.Literal(randomId(), Space.EMPTY, Markers.EMPTY, userPart, null)); + } + if (!groupPart.isEmpty()) { + groupContents.add(new Docker.Literal(randomId(), Space.EMPTY, Markers.EMPTY, groupPart, null)); + } + } else { + // Add to the appropriate list + if (foundColon) { + groupContents.add(content); + } else { + userContents.add(content); + } + } + } else { + // Environment variables or quoted strings + if (foundColon) { + groupContents.add(content); + } else { + userContents.add(content); + } + } + } + + Docker.Argument user = new Docker.Argument(randomId(), prefix, Markers.EMPTY, userContents); + Docker.Argument group = groupContents.isEmpty() ? null : + new Docker.Argument(randomId(), Space.EMPTY, Markers.EMPTY, groupContents); + + return new Docker.@Nullable Argument[]{user, group}; + } + + @Override + public Docker visitStopsignalInstruction(DockerParser.StopsignalInstructionContext ctx) { + Space prefix = prefix(ctx.getStart()); + + String stopsignalKeyword = ctx.STOPSIGNAL().getText(); + skip(ctx.STOPSIGNAL().getSymbol()); + + Docker.Argument signal = visitArgument(ctx.signal()); + + // Advance cursor to end of instruction + if (ctx.getStop() != null) { + advanceCursor(ctx.getStop().getStopIndex() + 1); + } + + return new Docker.Stopsignal(randomId(), prefix, Markers.EMPTY, stopsignalKeyword, signal); + } + + @Override + public Docker visitOnbuildInstruction(DockerParser.OnbuildInstructionContext ctx) { + Space prefix = prefix(ctx.getStart()); + + String onbuildKeyword = ctx.ONBUILD().getText(); + skip(ctx.ONBUILD().getSymbol()); + + // Visit the wrapped instruction + Docker.Instruction instruction = (Docker.Instruction) visit(ctx.instruction()); + + // Advance cursor to end of instruction + if (ctx.getStop() != null) { + advanceCursor(ctx.getStop().getStopIndex() + 1); + } + + return new Docker.Onbuild(randomId(), prefix, Markers.EMPTY, onbuildKeyword, instruction); + } + + @Override + public Docker visitHealthcheckInstruction(DockerParser.HealthcheckInstructionContext ctx) { + Space prefix = prefix(ctx.getStart()); + + String healthcheckKeyword = ctx.HEALTHCHECK().getText(); + skip(ctx.HEALTHCHECK().getSymbol()); + + List flags = null; + Space nonePrefix = Space.EMPTY; + Docker.@Nullable Cmd cmd = null; + + // Check if this is HEALTHCHECK NONE + if (ctx.NONE() != null) { + // HEALTHCHECK NONE - capture the whitespace before NONE, cmd stays null + nonePrefix = prefix(ctx.NONE().getSymbol()); + skip(ctx.NONE().getSymbol()); + } else { + // HEALTHCHECK [options] CMD (execForm | shellForm) + if (ctx.healthcheckOptions() != null) { + flags = convertHealthcheckOptions(ctx.healthcheckOptions()); + } + + // Parse CMD and its form + Space cmdPrefix = prefix(ctx.CMD().getSymbol()); + String cmdKeywordText = ctx.CMD().getText(); + skip(ctx.CMD().getSymbol()); + + Docker.CommandForm form; + if (ctx.execForm() != null) { + form = visitExecFormContext(ctx.execForm()); + } else if (ctx.shellForm() != null) { + form = visitShellFormContext(ctx.shellForm()); + } else { + throw new IllegalStateException("HEALTHCHECK must have either execForm or shellForm after CMD"); + } + + cmd = new Docker.Cmd(randomId(), cmdPrefix, Markers.EMPTY, cmdKeywordText, form); + } + + // Advance cursor to end of instruction + if (ctx.getStop() != null) { + advanceCursor(ctx.getStop().getStopIndex() + 1); + } + + return new Docker.Healthcheck(randomId(), prefix, Markers.EMPTY, healthcheckKeyword, flags, nonePrefix, cmd); + } + + private List convertHealthcheckOptions(DockerParser.HealthcheckOptionsContext ctx) { + List flags = new ArrayList<>(); + for (DockerParser.HealthcheckOptionContext optCtx : ctx.healthcheckOption()) { + flags.add(parseFlag(optCtx.FLAG().getSymbol())); + } + return flags; + } + + @Override + public Docker visitMaintainerInstruction(DockerParser.MaintainerInstructionContext ctx) { + Space prefix = prefix(ctx.getStart()); + + String maintainerKeyword = ctx.MAINTAINER().getText(); + skip(ctx.MAINTAINER().getSymbol()); + + Docker.Argument text = visitArgument(ctx.text()); + + // Advance cursor to end of instruction + if (ctx.getStop() != null) { + advanceCursor(ctx.getStop().getStopIndex() + 1); + } + + return new Docker.Maintainer(randomId(), prefix, Markers.EMPTY, maintainerKeyword, text); + } + + private Docker.CommandForm visitCommandFormForRun(DockerParser.RunInstructionContext ctx) { + if (ctx.execForm() != null) { + return visitExecFormContext(ctx.execForm()); + } else if (ctx.shellForm() != null) { + return visitShellFormContext(ctx.shellForm()); + } else if (ctx.heredoc() != null) { + return visitHeredocContext(ctx.heredoc(), false); + } else { + // Fallback to empty shell form + return new Docker.ShellForm(randomId(), Space.EMPTY, Markers.EMPTY, new Docker.Literal(randomId(), Space.EMPTY, Markers.EMPTY, "", null)); + } + } + + private Docker.CommandForm visitCommandFormForCmd(DockerParser.CmdInstructionContext ctx) { + if (ctx.execForm() != null) { + return visitExecFormContext(ctx.execForm()); + } else if (ctx.shellForm() != null) { + return visitShellFormContext(ctx.shellForm()); + } else { + return new Docker.ShellForm(randomId(), Space.EMPTY, Markers.EMPTY, new Docker.Literal(randomId(), Space.EMPTY, Markers.EMPTY, "", null)); + } + } + + private Docker.CommandForm visitCommandFormForEntrypoint(DockerParser.EntrypointInstructionContext ctx) { + if (ctx.execForm() != null) { + return visitExecFormContext(ctx.execForm()); + } else if (ctx.shellForm() != null) { + return visitShellFormContext(ctx.shellForm()); + } else { + return new Docker.ShellForm(randomId(), Space.EMPTY, Markers.EMPTY, new Docker.Literal(randomId(), Space.EMPTY, Markers.EMPTY, "", null)); + } + } + + private List convertFlags(DockerParser.FlagsContext ctx) { + List flags = new ArrayList<>(); + for (DockerParser.FlagContext flagCtx : ctx.flag()) { + flags.add(parseFlag(flagCtx.FLAG().getSymbol())); + } + return flags; + } + + /** + * Parse a FLAG token into a Flag AST node. + * FLAG token format: --name or --name=value + */ + private Docker.Flag parseFlag(Token flagToken) { + Space flagPrefix = prefix(flagToken); + String tokenText = flagToken.getText(); + skip(flagToken); + + // Parse the flag text: --name or --name=value + // Skip the leading "--" + String flagContent = tokenText.substring(2); + + String flagName; + Docker.Argument flagValue = null; + + int equalsIdx = flagContent.indexOf('='); + if (equalsIdx >= 0) { + flagName = flagContent.substring(0, equalsIdx); + String valueText = flagContent.substring(equalsIdx + 1); + + // Parse the value text into its components + List contents = parseFlagValueText(valueText); + flagValue = new Docker.Argument(randomId(), Space.EMPTY, Markers.EMPTY, contents); + } else { + flagName = flagContent; + } + + return new Docker.Flag(randomId(), flagPrefix, Markers.EMPTY, flagName, flagValue); + } + + /** + * Parse a flag value text into its component parts. + * Recognizes environment variables ($VAR, ${VAR}), and splits on = signs. + */ + private List parseFlagValueText(String text) { + List contents = new ArrayList<>(); + StringBuilder current = new StringBuilder(); + int i = 0; + + while (i < text.length()) { + char c = text.charAt(i); + + if (c == '$') { + // Flush any accumulated text + if (current.length() > 0) { + contents.add(new Docker.Literal(randomId(), Space.EMPTY, Markers.EMPTY, current.toString(), null)); + current.setLength(0); + } + + // Parse environment variable + if (i + 1 < text.length() && text.charAt(i + 1) == '{') { + // Braced form: ${VAR} + int endBrace = text.indexOf('}', i + 2); + if (endBrace > i + 2) { + String varContent = text.substring(i + 2, endBrace); + // Handle ${VAR:-default} and similar forms + String varName = varContent; + int colonIdx = varContent.indexOf(':'); + if (colonIdx >= 0) { + varName = varContent.substring(0, colonIdx); + } + contents.add(new Docker.EnvironmentVariable(randomId(), Space.EMPTY, Markers.EMPTY, varName, true)); + i = endBrace + 1; + continue; + } + } else if (i + 1 < text.length()) { + // Unbraced form: $VAR + int varStart = i + 1; + int varEnd = varStart; + while (varEnd < text.length() && isVarChar(text.charAt(varEnd), varEnd == varStart)) { + varEnd++; + } + if (varEnd > varStart) { + String varName = text.substring(varStart, varEnd); + contents.add(new Docker.EnvironmentVariable(randomId(), Space.EMPTY, Markers.EMPTY, varName, false)); + i = varEnd; + continue; + } + } + // Not a valid env var, treat $ as literal + current.append(c); + i++; + } else if (c == '=') { + // Flush any accumulated text + if (current.length() > 0) { + contents.add(new Docker.Literal(randomId(), Space.EMPTY, Markers.EMPTY, current.toString(), null)); + current.setLength(0); + } + // Add the equals sign as its own element + contents.add(new Docker.Literal(randomId(), Space.EMPTY, Markers.EMPTY, "=", null)); + i++; + } else { + current.append(c); + i++; + } + } + + // Flush remaining text + if (current.length() > 0) { + contents.add(new Docker.Literal(randomId(), Space.EMPTY, Markers.EMPTY, current.toString(), null)); + } + + return contents.isEmpty() ? singletonList(new Docker.Literal(randomId(), Space.EMPTY, Markers.EMPTY, "", null)) : contents; + } + + private boolean isVarChar(char c, boolean isFirst) { + if (isFirst) { + return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || c == '_'; + } + return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || c == '_'; + } + + private Docker.ShellForm visitShellFormContext(DockerParser.ShellFormContext ctx) { + return convert(ctx, (c, prefix) -> { + // Parse the shell form text as a single Literal + int startIndex = c.shellFormText().getStart().getStartIndex(); + int stopIndex = c.shellFormText().getStop().getStopIndex(); + int startCharIndex = source.offsetByCodePoints(0, startIndex); + int stopCharIndex = source.offsetByCodePoints(0, stopIndex + 1); + String fullText = source.substring(startCharIndex, stopCharIndex); + + Docker.Literal literal = new Docker.Literal( + randomId(), + Space.EMPTY, + Markers.EMPTY, + fullText, + null + ); + return new Docker.ShellForm( + randomId(), + prefix, + Markers.EMPTY, + literal + ); + }); + } + + private Docker.ExecForm visitExecFormContext(DockerParser.ExecFormContext ctx) { + return convert(ctx, (c, prefix) -> { + DockerParser.JsonArrayContext jsonArray = c.jsonArray(); + return visitJsonArrayAsExecFormInternal(jsonArray, prefix); + }); + } + + /** + * Parse a JSON array directly as an ExecForm (used by COPY/ADD instructions) + */ + private Docker.ExecForm visitJsonArrayAsExecForm(DockerParser.JsonArrayContext ctx) { + Space prefix = prefix(ctx.getStart()); + return visitJsonArrayAsExecFormInternal(ctx, prefix); + } + + private Docker.ExecForm visitJsonArrayAsExecFormInternal(DockerParser.JsonArrayContext jsonArray, Space prefix) { + List args = new ArrayList<>(); + + skip(jsonArray.LBRACKET().getSymbol()); + + if (jsonArray.jsonArrayElements() != null) { + DockerParser.JsonArrayElementsContext elementsCtx = jsonArray.jsonArrayElements(); + List jsonStrings = elementsCtx.jsonString(); + List commas = elementsCtx.COMMA(); + + for (int i = 0; i < jsonStrings.size(); i++) { + DockerParser.JsonStringContext jsonStr = jsonStrings.get(i); + + // Capture only whitespace before this element (not the comma) + // For first element: captures whitespace after [ + // For subsequent elements: captures whitespace after comma + Space argPrefix = prefix(jsonStr.getStart()); + String value = jsonStr.DOUBLE_QUOTED_STRING().getText(); + // Remove surrounding quotes + if (value.startsWith("\"") && value.endsWith("\"")) { + value = value.substring(1, value.length() - 1); + } + advanceCursor(jsonStr.DOUBLE_QUOTED_STRING().getSymbol().getStopIndex() + 1); + + args.add(new Docker.Literal( + randomId(), + argPrefix, + Markers.EMPTY, + value, + Docker.Literal.QuoteStyle.DOUBLE + )); + + // Skip the comma after this element (if not the last element) + if (i < commas.size()) { + skip(commas.get(i).getSymbol()); + } + } + } + + // Capture whitespace before closing bracket to preserve " ]" vs "]" + Space closingBracketPrefix = prefix(jsonArray.RBRACKET().getSymbol()); + skip(jsonArray.RBRACKET().getSymbol()); + + return new Docker.ExecForm(randomId(), prefix, Markers.EMPTY, args, closingBracketPrefix); + } + + /** + * Parse an exec form from a string like: {@code ["curl", "-f", "http://localhost/"]} + * Used when the ANTLR grammar's greedy flagValue rule has consumed the exec form tokens. + * Note: Commas are skipped during parsing and printed explicitly by the printer. + */ + private Docker.ExecForm parseExecFormFromText(String text) { + List args = new ArrayList<>(); + + // Find leading whitespace before [ + int i = 0; + while (i < text.length() && Character.isWhitespace(text.charAt(i))) { + i++; + } + Space prefix = Space.format(text.substring(0, i)); + + // Find opening bracket + if (i >= text.length() || text.charAt(i) != '[') { + // Fallback - return empty exec form + return new Docker.ExecForm(randomId(), prefix, Markers.EMPTY, args, Space.EMPTY); + } + i++; // skip [ + + // Parse elements: "string" separated by commas + while (i < text.length()) { + // Capture prefix: whitespace only (not comma) + int prefixStart = i; + + // Skip whitespace + while (i < text.length() && Character.isWhitespace(text.charAt(i))) { + i++; + } + + if (i >= text.length()) break; + + // Check for closing bracket + if (text.charAt(i) == ']') { + // Closing bracket found - remaining whitespace is the closing bracket prefix + return new Docker.ExecForm(randomId(), prefix, Markers.EMPTY, args, + Space.format(text.substring(prefixStart, i))); + } + + // Handle comma - skip it and capture only whitespace after it + if (text.charAt(i) == ',') { + i++; // skip comma + prefixStart = i; // start prefix after comma + // Skip whitespace after comma + while (i < text.length() && Character.isWhitespace(text.charAt(i))) { + i++; + } + } + + if (i >= text.length()) break; + + // Check for closing bracket again + if (text.charAt(i) == ']') { + return new Docker.ExecForm(randomId(), prefix, Markers.EMPTY, args, + Space.format(text.substring(prefixStart, i))); + } + + String elemPrefixStr = text.substring(prefixStart, i); + Space elemPrefix = Space.format(elemPrefixStr); + + // Parse quoted string + if (text.charAt(i) == '"') { + i++; // skip opening quote + StringBuilder value = new StringBuilder(); + while (i < text.length() && text.charAt(i) != '"') { + // Handle escapes + if (text.charAt(i) == '\\' && i + 1 < text.length()) { + i++; // skip backslash + value.append(text.charAt(i)); + } else { + value.append(text.charAt(i)); + } + i++; + } + if (i < text.length() && text.charAt(i) == '"') { + i++; // skip closing quote + } + args.add(new Docker.Literal(randomId(), elemPrefix, Markers.EMPTY, value.toString(), + Docker.Literal.QuoteStyle.DOUBLE)); + } else { + // Unexpected - skip to next comma or bracket + while (i < text.length() && text.charAt(i) != ',' && text.charAt(i) != ']') { + i++; + } + } + } + + return new Docker.ExecForm(randomId(), prefix, Markers.EMPTY, args, Space.EMPTY); + } + + /** + * Visit a heredoc context using the unified structure. + * Handles both single heredocs (RUN < { + // Build the preamble from the heredocPreamble context + DockerParser.HeredocPreambleContext preambleCtx = c.heredocPreamble(); + StringBuilder preambleBuilder = new StringBuilder(); + Docker.Argument destination = null; + + // Track if we've seen the first HEREDOC_START (for destination extraction) + boolean seenFirstHeredocStart = false; + int heredocStartCount = 0; + + // Collect all tokens in the preamble (HEREDOC_START and preambleElements) + for (int i = 0; i < preambleCtx.getChildCount(); i++) { + ParseTree child = preambleCtx.getChild(i); + if (child instanceof TerminalNode) { + TerminalNode tn = (TerminalNode) child; + if (tn.getSymbol().getType() == DockerLexer.HEREDOC_START) { + heredocStartCount++; + seenFirstHeredocStart = true; + } + // Add whitespace prefix if any + Space tokenPrefix = prefix(tn.getSymbol()); + preambleBuilder.append(tokenPrefix.getWhitespace()); + preambleBuilder.append(tn.getText()); + skip(tn.getSymbol()); + } else if (child instanceof DockerParser.PreambleElementContext) { + DockerParser.PreambleElementContext elemCtx = (DockerParser.PreambleElementContext) child; + Token token = elemCtx.getStart(); + Space tokenPrefix = prefix(token); + + // For single heredoc COPY/ADD, extract destination from first element after HEREDOC_START + // The destination is NOT included in the preamble - it's stored separately + if (extractDestination && seenFirstHeredocStart && heredocStartCount == 1 && destination == null) { + // This is the destination path for COPY/ADD + destination = createArgumentFromPreambleElement(elemCtx, tokenPrefix); + skip(token); + } else { + // Add to preamble (not a destination) + preambleBuilder.append(tokenPrefix.getWhitespace()); + preambleBuilder.append(token.getText()); + skip(token); + } + } + } + + String preamble = preambleBuilder.toString(); + + // Skip the NEWLINE after the preamble + if (c.NEWLINE() != null) { + skip(c.NEWLINE().getSymbol()); + } + + // Parse each heredoc body + List bodies = new ArrayList<>(); + for (DockerParser.HeredocBodyContext bodyCtx : c.heredocBody()) { + bodies.add(visitHeredocBodyContext(bodyCtx)); + } + + return new Docker.HeredocForm(randomId(), prefix, Markers.EMPTY, preamble, destination, bodies); + }); + } + + /** + * Create an Argument from a preamble element (for destination extraction in COPY/ADD). + * The prefix is passed in since it was already consumed when building the preamble. + */ + private Docker.Argument createArgumentFromPreambleElement(DockerParser.PreambleElementContext ctx, Space argPrefix) { + List contents = new ArrayList<>(); + Token token = ctx.getStart(); + String text = token.getText(); + + // Create a literal for the element + Docker.Literal.QuoteStyle quoteStyle = null; + if (ctx.DOUBLE_QUOTED_STRING() != null) { + quoteStyle = Docker.Literal.QuoteStyle.DOUBLE; + text = text.substring(1, text.length() - 1); // Remove quotes + } else if (ctx.SINGLE_QUOTED_STRING() != null) { + quoteStyle = Docker.Literal.QuoteStyle.SINGLE; + text = text.substring(1, text.length() - 1); // Remove quotes + } + + contents.add(new Docker.Literal(randomId(), Space.EMPTY, Markers.EMPTY, text, quoteStyle)); + return new Docker.Argument(randomId(), argPrefix, Markers.EMPTY, contents); + } + + private Docker.HeredocBody visitHeredocBodyContext(DockerParser.HeredocBodyContext ctx) { + Space prefix = prefix(ctx.getStart()); + + // Collect content lines from heredocContent + List contentLines = new ArrayList<>(); + if (ctx.heredocContent() != null) { + collectHeredocContent(ctx.heredocContent(), contentLines); + } + + // Get closing marker (UNQUOTED_TEXT) - this is also the opening marker name without << + String closing = ctx.heredocEnd().UNQUOTED_TEXT().getText(); + skip(ctx.heredocEnd().UNQUOTED_TEXT().getSymbol()); + + // The opening marker is "<<" + closing (we reconstruct it for the model) + String opening = "<<" + closing; + + return new Docker.HeredocBody(randomId(), prefix, Markers.EMPTY, opening, contentLines, closing); + } + + /** + * Helper method to collect heredoc content lines from a HeredocContentContext. + */ + private void collectHeredocContent(DockerParser.HeredocContentContext contentCtx, List contentLines) { + StringBuilder currentLine = new StringBuilder(); + + for (int i = 0; i < contentCtx.getChildCount(); i++) { + ParseTree child = contentCtx.getChild(i); + + if (child instanceof TerminalNode) { + TerminalNode tn = (TerminalNode) child; + if (tn.getSymbol().getType() == DockerLexer.NEWLINE) { + // Newline - end current line and start new one + currentLine.append(tn.getText()); + skip(tn.getSymbol()); + contentLines.add(currentLine.toString()); + currentLine = new StringBuilder(); + } else if (tn.getSymbol().getType() == DockerLexer.HEREDOC_CONTENT) { + // Heredoc content line + currentLine.append(tn.getText()); + skip(tn.getSymbol()); + } + } + } + + // Add any remaining content as a line + if (currentLine.length() > 0) { + contentLines.add(currentLine.toString()); + } + } + + private Docker.Argument visitArgument(@Nullable ParserRuleContext ctx) { + if (ctx == null) { + return new Docker.Argument(randomId(), Space.EMPTY, Markers.EMPTY, emptyList()); + } + + return convert(ctx, (c, prefix) -> { + // Read from source to include HIDDEN channel tokens (whitespace, comments) + int startIndex = c.getStart().getStartIndex(); + int stopIndex = c.getStop().getStopIndex(); + + // Defensive check for invalid ranges + if (stopIndex < startIndex) { + return new Docker.Argument(randomId(), prefix, Markers.EMPTY, emptyList()); + } + + int startCharIndex = source.offsetByCodePoints(0, startIndex); + int stopCharIndex = source.offsetByCodePoints(0, stopIndex + 1); + + if (stopCharIndex < startCharIndex) { + return new Docker.Argument(randomId(), prefix, Markers.EMPTY, emptyList()); + } + + String fullText = source.substring(startCharIndex, stopCharIndex); + + Docker.Literal plainText = new Docker.Literal( + randomId(), + Space.EMPTY, + Markers.EMPTY, + fullText, + null + ); + return new Docker.Argument(randomId(), prefix, Markers.EMPTY, singletonList(plainText)); + }); + } + + /** + * Parse an ENV_VAR token text (e.g., "${VAR}" or "$VAR") into an EnvironmentVariable content + */ + private Docker.EnvironmentVariable createEnvVar(String text) { + boolean braced = text.startsWith("${"); + String varName = braced ? text.substring(2, text.length() - 1) : text.substring(1); + return new Docker.EnvironmentVariable(randomId(), Space.EMPTY, Markers.EMPTY, varName, braced); + } + + // Helper methods for cursor management + + private Space prefix(ParserRuleContext ctx) { + return prefix(ctx.getStart()); + } + + private Space prefix(Token token) { + int start = token.getStartIndex(); + if (start < codePointCursor) { + return Space.EMPTY; + } + return Space.format(source, cursor, advanceCursor(start)); + } + + private int advanceCursor(int newCodePointIndex) { + if (newCodePointIndex <= codePointCursor) { + return cursor; + } + cursor = source.offsetByCodePoints(cursor, newCodePointIndex - codePointCursor); + codePointCursor = newCodePointIndex; + return cursor; + } + + private void skip(@Nullable Token token) { + if (token != null) { + advanceCursor(token.getStopIndex() + 1); + } + } + + private T convert(C ctx, BiFunction conversion) { + T t = conversion.apply(ctx, prefix(ctx)); + if (ctx.getStop() != null) { + advanceCursor(ctx.getStop().getStopIndex() + 1); + } + return t; + } + + /** + * Helper class to hold both the arguments and closing bracket prefix when parsing JSON arrays. + */ + private static class JsonArrayParseResult { + final List arguments; + final Space closingBracketPrefix; + + JsonArrayParseResult(List arguments, Space closingBracketPrefix) { + this.arguments = arguments; + this.closingBracketPrefix = closingBracketPrefix; + } + } +} diff --git a/rewrite-docker/src/main/java/org/openrewrite/docker/internal/DockerPrinter.java b/rewrite-docker/src/main/java/org/openrewrite/docker/internal/DockerPrinter.java new file mode 100644 index 0000000000..001b0fc3a8 --- /dev/null +++ b/rewrite-docker/src/main/java/org/openrewrite/docker/internal/DockerPrinter.java @@ -0,0 +1,483 @@ +/* + * Copyright 2026 the original author or authors. + *

+ * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * https://www.apache.org/licenses/LICENSE-2.0 + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.openrewrite.docker.internal; + +import org.openrewrite.PrintOutputCapture; +import org.openrewrite.docker.DockerVisitor; +import org.openrewrite.docker.tree.Comment; +import org.openrewrite.docker.tree.Docker; +import org.openrewrite.docker.tree.Space; +import org.openrewrite.marker.Marker; + +import java.util.List; + +public class DockerPrinter

extends DockerVisitor> { + + @Override + public Space visitSpace(Space space, PrintOutputCapture

p) { + for (Comment comment : space.getComments()) { + p.append(comment.getPrefix()); + p.append(comment.getText()); + } + p.append(space.getWhitespace()); + return space; + } + + @Override + public Docker visitFile(Docker.File file, PrintOutputCapture

p) { + beforeSyntax(file, p); + + // Print global ARG instructions + for (Docker.Arg arg : file.getGlobalArgs()) { + visit(arg, p); + } + + // Print build stages + for (Docker.Stage stage : file.getStages()) { + visit(stage, p); + } + + visitSpace(file.getEof(), p); + afterSyntax(file, p); + return file; + } + + @Override + public Docker visitStage(Docker.Stage stage, PrintOutputCapture

p) { + beforeSyntax(stage, p); + + // Print FROM instruction + visit(stage.getFrom(), p); + + // Print stage instructions + for (Docker.Instruction instruction : stage.getInstructions()) { + visit(instruction, p); + } + + afterSyntax(stage, p); + return stage; + } + + @Override + public Docker visitFrom(Docker.From from, PrintOutputCapture

p) { + beforeSyntax(from, p); + p.append(from.getKeyword()); + if (from.getFlags() != null) { + for (Docker.Flag flag : from.getFlags()) { + visit(flag, p); + } + } + visit(from.getImageName(), p); + if (from.getTag() != null) { + p.append(":"); + visit(from.getTag(), p); + } else if (from.getDigest() != null) { + p.append("@"); + visit(from.getDigest(), p); + } + if (from.getAs() != null) { + visitSpace(from.getAs().getPrefix(), p); + p.append(from.getAs().getKeyword()); + visit(from.getAs().getName(), p); + } + afterSyntax(from, p); + return from; + } + + @Override + public Docker visitRun(Docker.Run run, PrintOutputCapture

p) { + beforeSyntax(run, p); + p.append(run.getKeyword()); + if (run.getFlags() != null) { + for (Docker.Flag flag : run.getFlags()) { + visit(flag, p); + } + } + visit(run.getCommand(), p); + afterSyntax(run, p); + return run; + } + + @Override + public Docker visitAdd(Docker.Add add, PrintOutputCapture

p) { + beforeSyntax(add, p); + p.append(add.getKeyword()); + if (add.getFlags() != null) { + for (Docker.Flag flag : add.getFlags()) { + visit(flag, p); + } + } + visit(add.getForm(), p); + afterSyntax(add, p); + return add; + } + + @Override + public Docker visitCopy(Docker.Copy copy, PrintOutputCapture

p) { + beforeSyntax(copy, p); + p.append(copy.getKeyword()); + if (copy.getFlags() != null) { + for (Docker.Flag flag : copy.getFlags()) { + visit(flag, p); + } + } + visit(copy.getForm(), p); + afterSyntax(copy, p); + return copy; + } + + @Override + public Docker visitArg(Docker.Arg arg, PrintOutputCapture

p) { + beforeSyntax(arg, p); + p.append(arg.getKeyword()); + visit(arg.getName(), p); + if (arg.getValue() != null) { + p.append("="); + visit(arg.getValue(), p); + } + afterSyntax(arg, p); + return arg; + } + + @Override + public Docker visitEnv(Docker.Env env, PrintOutputCapture

p) { + beforeSyntax(env, p); + p.append(env.getKeyword()); + for (Docker.Env.EnvPair pair : env.getPairs()) { + visitSpace(pair.getPrefix(), p); + visit(pair.getKey(), p); + if (pair.isHasEquals()) { + p.append("="); + } + visit(pair.getValue(), p); + } + afterSyntax(env, p); + return env; + } + + @Override + public Docker visitLabel(Docker.Label label, PrintOutputCapture

p) { + beforeSyntax(label, p); + p.append(label.getKeyword()); + for (Docker.Label.LabelPair pair : label.getPairs()) { + visitSpace(pair.getPrefix(), p); + visit(pair.getKey(), p); + if (pair.isHasEquals()) { + p.append("="); + } + visit(pair.getValue(), p); + } + afterSyntax(label, p); + return label; + } + + @Override + public Docker visitCmd(Docker.Cmd cmd, PrintOutputCapture

p) { + beforeSyntax(cmd, p); + p.append(cmd.getKeyword()); + visit(cmd.getCommand(), p); + afterSyntax(cmd, p); + return cmd; + } + + @Override + public Docker visitEntrypoint(Docker.Entrypoint entrypoint, PrintOutputCapture

p) { + beforeSyntax(entrypoint, p); + p.append(entrypoint.getKeyword()); + visit(entrypoint.getCommand(), p); + afterSyntax(entrypoint, p); + return entrypoint; + } + + @Override + public Docker visitExpose(Docker.Expose expose, PrintOutputCapture

p) { + beforeSyntax(expose, p); + p.append(expose.getKeyword()); + for (Docker.Port port : expose.getPorts()) { + visit(port, p); + } + afterSyntax(expose, p); + return expose; + } + + @Override + public Docker visitPort(Docker.Port port, PrintOutputCapture

p) { + beforeSyntax(port, p); + p.append(port.getText()); + afterSyntax(port, p); + return port; + } + + @Override + public Docker visitVolume(Docker.Volume volume, PrintOutputCapture

p) { + beforeSyntax(volume, p); + p.append(volume.getKeyword()); + if (volume.isJsonForm()) { + // Print the space before [ and the bracket + visitSpace(volume.getOpeningBracketPrefix(), p); + p.append("["); + for (int i = 0; i < volume.getValues().size(); i++) { + Docker.Argument arg = volume.getValues().get(i); + // Print the argument with its prefix (includes space after [ or after ,) + visit(arg, p); + // Print comma after this element if not last + if (i < volume.getValues().size() - 1) { + p.append(","); + } + } + visitSpace(volume.getClosingBracketPrefix(), p); + p.append("]"); + } else { + for (Docker.Argument value : volume.getValues()) { + visit(value, p); + } + } + afterSyntax(volume, p); + return volume; + } + + @Override + public Docker visitShell(Docker.Shell shell, PrintOutputCapture

p) { + beforeSyntax(shell, p); + p.append(shell.getKeyword()); + // Print the space before [ and the bracket + visitSpace(shell.getOpeningBracketPrefix(), p); + p.append("["); + for (int i = 0; i < shell.getArguments().size(); i++) { + Docker.Argument arg = shell.getArguments().get(i); + // Print the argument with its prefix (includes space after [ or after ,) + visit(arg, p); + // Print comma after this element if not last + if (i < shell.getArguments().size() - 1) { + p.append(","); + } + } + visitSpace(shell.getClosingBracketPrefix(), p); + p.append("]"); + afterSyntax(shell, p); + return shell; + } + + @Override + public Docker visitWorkdir(Docker.Workdir workdir, PrintOutputCapture

p) { + beforeSyntax(workdir, p); + p.append(workdir.getKeyword()); + visit(workdir.getPath(), p); + afterSyntax(workdir, p); + return workdir; + } + + @Override + public Docker visitUser(Docker.User user, PrintOutputCapture

p) { + beforeSyntax(user, p); + p.append(user.getKeyword()); + visit(user.getUser(), p); + if (user.getGroup() != null) { + p.append(":"); + visit(user.getGroup(), p); + } + afterSyntax(user, p); + return user; + } + + @Override + public Docker visitStopsignal(Docker.Stopsignal stopsignal, PrintOutputCapture

p) { + beforeSyntax(stopsignal, p); + p.append(stopsignal.getKeyword()); + visit(stopsignal.getSignal(), p); + afterSyntax(stopsignal, p); + return stopsignal; + } + + @Override + public Docker visitOnbuild(Docker.Onbuild onbuild, PrintOutputCapture

p) { + beforeSyntax(onbuild, p); + p.append(onbuild.getKeyword()); + visit(onbuild.getInstruction(), p); + afterSyntax(onbuild, p); + return onbuild; + } + + @Override + public Docker visitHealthcheck(Docker.Healthcheck healthcheck, PrintOutputCapture

p) { + beforeSyntax(healthcheck, p); + p.append(healthcheck.getKeyword()); + if (healthcheck.isNone()) { + visitSpace(healthcheck.getNonePrefix(), p); + p.append("NONE"); + } else { + if (healthcheck.getFlags() != null) { + for (Docker.Flag flag : healthcheck.getFlags()) { + visit(flag, p); + } + } + if (healthcheck.getCmd() != null) { + visit(healthcheck.getCmd(), p); + } + } + afterSyntax(healthcheck, p); + return healthcheck; + } + + @Override + public Docker visitMaintainer(Docker.Maintainer maintainer, PrintOutputCapture

p) { + beforeSyntax(maintainer, p); + p.append(maintainer.getKeyword()); + visit(maintainer.getText(), p); + afterSyntax(maintainer, p); + return maintainer; + } + + @Override + public Docker visitShellForm(Docker.ShellForm shellForm, PrintOutputCapture

p) { + beforeSyntax(shellForm, p); + visit(shellForm.getArgument(), p); + afterSyntax(shellForm, p); + return shellForm; + } + + @Override + public Docker visitExecForm(Docker.ExecForm execForm, PrintOutputCapture

p) { + beforeSyntax(execForm, p); + p.append("["); + List arguments = execForm.getArguments(); + for (int i = 0; i < arguments.size(); i++) { + Docker.Literal argument = arguments.get(i); + visit(argument, p); + // Print comma after this element if not last + if (i < arguments.size() - 1) { + p.append(","); + } + } + visitSpace(execForm.getClosingBracketPrefix(), p); + p.append("]"); + afterSyntax(execForm, p); + return execForm; + } + + @Override + public Docker visitHeredocForm(Docker.HeredocForm heredocForm, PrintOutputCapture

p) { + beforeSyntax(heredocForm, p); + // Print the preamble (heredoc marker(s) and optional shell commands) + p.append(heredocForm.getPreamble()); + // Print destination if present (for COPY/ADD heredocs) + visit(heredocForm.getDestination(), p); + p.append("\n"); + // Print each heredoc body - content lines already include their newlines + for (Docker.HeredocBody body : heredocForm.getBodies()) { + visit(body, p); + } + afterSyntax(heredocForm, p); + return heredocForm; + } + + @Override + public Docker visitHeredocBody(Docker.HeredocBody heredocBody, PrintOutputCapture

p) { + beforeSyntax(heredocBody, p); + for (String contentLine : heredocBody.getContentLines()) { + p.append(contentLine); + } + p.append(heredocBody.getClosing()); + afterSyntax(heredocBody, p); + return heredocBody; + } + + @Override + public Docker visitCopyShellForm(Docker.CopyShellForm copyShellForm, PrintOutputCapture

p) { + beforeSyntax(copyShellForm, p); + for (Docker.Argument source : copyShellForm.getSources()) { + visit(source, p); + } + visit(copyShellForm.getDestination(), p); + afterSyntax(copyShellForm, p); + return copyShellForm; + } + + @Override + public Docker visitFlag(Docker.Flag flag, PrintOutputCapture

p) { + beforeSyntax(flag, p); + p.append("--").append(flag.getName()); + if (flag.getValue() != null) { + p.append("="); + visit(flag.getValue(), p); + } + afterSyntax(flag, p); + return flag; + } + + @Override + public Docker visitArgument(Docker.Argument argument, PrintOutputCapture

p) { + beforeSyntax(argument, p); + for (Docker.ArgumentContent content : argument.getContents()) { + visit(content, p); + } + afterSyntax(argument, p); + return argument; + } + + @Override + public Docker visitLiteral(Docker.Literal literal, PrintOutputCapture

p) { + beforeSyntax(literal, p); + if (literal.getQuoteStyle() != null) { + char quote = literal.getQuoteStyle() == Docker.Literal.QuoteStyle.DOUBLE ? '"' : '\''; + p.append(quote).append(literal.getText()).append(quote); + } else { + p.append(literal.getText()); + } + afterSyntax(literal, p); + return literal; + } + + @Override + public Docker visitEnvironmentVariable(Docker.EnvironmentVariable environmentVariable, PrintOutputCapture

p) { + beforeSyntax(environmentVariable, p); + if (environmentVariable.isBraced()) { + p.append("${").append(environmentVariable.getName()).append("}"); + } else { + p.append("$").append(environmentVariable.getName()); + } + afterSyntax(environmentVariable, p); + return environmentVariable; + } + + private static final java.util.function.UnaryOperator DOCKERFILE_MARKER_WRAPPER = + out -> "~~" + out + (out.isEmpty() ? "" : "~~") + ">"; + + private void beforeSyntax(Docker d, PrintOutputCapture

p) { + beforeSyntax(d.getPrefix(), d.getMarkers(), p); + } + + private void beforeSyntax(Space prefix, org.openrewrite.marker.Markers markers, PrintOutputCapture

p) { + for (Marker marker : markers.getMarkers()) { + p.append(p.getMarkerPrinter().beforePrefix(marker, new org.openrewrite.Cursor(getCursor(), marker), DOCKERFILE_MARKER_WRAPPER)); + } + visitSpace(prefix, p); + visitMarkers(markers, p); + for (Marker marker : markers.getMarkers()) { + p.append(p.getMarkerPrinter().beforeSyntax(marker, new org.openrewrite.Cursor(getCursor(), marker), DOCKERFILE_MARKER_WRAPPER)); + } + } + + private void afterSyntax(Docker d, PrintOutputCapture

p) { + afterSyntax(d.getMarkers(), p); + } + + private void afterSyntax(org.openrewrite.marker.Markers markers, PrintOutputCapture

p) { + for (Marker marker : markers.getMarkers()) { + p.append(p.getMarkerPrinter().afterSyntax(marker, new org.openrewrite.Cursor(getCursor(), marker), DOCKERFILE_MARKER_WRAPPER)); + } + } +} diff --git a/rewrite-docker/src/main/java/org/openrewrite/docker/internal/grammar/DockerLexer.interp b/rewrite-docker/src/main/java/org/openrewrite/docker/internal/grammar/DockerLexer.interp new file mode 100644 index 0000000000..398d1fb932 --- /dev/null +++ b/rewrite-docker/src/main/java/org/openrewrite/docker/internal/grammar/DockerLexer.interp @@ -0,0 +1,165 @@ +token literal names: +null +null +null +'FROM' +'RUN' +'CMD' +'NONE' +'LABEL' +'EXPOSE' +'ENV' +'ADD' +'COPY' +'ENTRYPOINT' +'VOLUME' +'USER' +'WORKDIR' +'ARG' +'ONBUILD' +'STOPSIGNAL' +'HEALTHCHECK' +'SHELL' +'MAINTAINER' +'AS' +null +null +'[' +']' +',' +'=' +null +'--' +null +null +null +null +null +null +null +null +null +null +null +null +null +null +'\n' + +token symbolic names: +null +PARSER_DIRECTIVE +COMMENT +FROM +RUN +CMD +NONE +LABEL +EXPOSE +ENV +ADD +COPY +ENTRYPOINT +VOLUME +USER +WORKDIR +ARG +ONBUILD +STOPSIGNAL +HEALTHCHECK +SHELL +MAINTAINER +AS +HEREDOC_START +LINE_CONTINUATION +LBRACKET +RBRACKET +COMMA +EQUALS +FLAG +DASH_DASH +DOUBLE_QUOTED_STRING +SINGLE_QUOTED_STRING +ENV_VAR +SPECIAL_VAR +COMMAND_SUBST +BACKTICK_SUBST +UNQUOTED_TEXT +WS +NEWLINE +HP_LINE_CONTINUATION +HP_WS +HP_COMMENT +HP_LINE_COMMENT +HEREDOC_CONTENT +H_NEWLINE + +rule names: +PARSER_DIRECTIVE +COMMENT +FROM +RUN +CMD +NONE +LABEL +EXPOSE +ENV +ADD +COPY +ENTRYPOINT +VOLUME +USER +WORKDIR +ARG +ONBUILD +STOPSIGNAL +HEALTHCHECK +SHELL +MAINTAINER +AS +HEREDOC_START +LINE_CONTINUATION +LBRACKET +RBRACKET +COMMA +EQUALS +FLAG +DASH_DASH +UNQUOTED_CHAR +ESCAPED_CHAR +DOUBLE_QUOTED_STRING +SINGLE_QUOTED_STRING +INLINE_CONTINUATION +ESCAPE_SEQUENCE +HEX_DIGIT +ENV_VAR +SPECIAL_VAR +COMMAND_SUBST +COMMAND_SUBST_INNER +BACKTICK_SUBST +UNQUOTED_TEXT +WS +WS_CHAR +NEWLINE +NEWLINE_CHAR +HP_LINE_CONTINUATION +HP_NEWLINE +HP_WS +HP_COMMENT +HP_LINE_COMMENT +HP_HEREDOC_START +HP_UNQUOTED_TEXT +H_NEWLINE +HEREDOC_CONTENT + +channel names: +DEFAULT_TOKEN_CHANNEL +HIDDEN + +mode names: +DEFAULT_MODE +HEREDOC_PREAMBLE +HEREDOC + +atn: +[4, 0, 45, 674, 6, -1, 6, -1, 6, -1, 2, 0, 7, 0, 2, 1, 7, 1, 2, 2, 7, 2, 2, 3, 7, 3, 2, 4, 7, 4, 2, 5, 7, 5, 2, 6, 7, 6, 2, 7, 7, 7, 2, 8, 7, 8, 2, 9, 7, 9, 2, 10, 7, 10, 2, 11, 7, 11, 2, 12, 7, 12, 2, 13, 7, 13, 2, 14, 7, 14, 2, 15, 7, 15, 2, 16, 7, 16, 2, 17, 7, 17, 2, 18, 7, 18, 2, 19, 7, 19, 2, 20, 7, 20, 2, 21, 7, 21, 2, 22, 7, 22, 2, 23, 7, 23, 2, 24, 7, 24, 2, 25, 7, 25, 2, 26, 7, 26, 2, 27, 7, 27, 2, 28, 7, 28, 2, 29, 7, 29, 2, 30, 7, 30, 2, 31, 7, 31, 2, 32, 7, 32, 2, 33, 7, 33, 2, 34, 7, 34, 2, 35, 7, 35, 2, 36, 7, 36, 2, 37, 7, 37, 2, 38, 7, 38, 2, 39, 7, 39, 2, 40, 7, 40, 2, 41, 7, 41, 2, 42, 7, 42, 2, 43, 7, 43, 2, 44, 7, 44, 2, 45, 7, 45, 2, 46, 7, 46, 2, 47, 7, 47, 2, 48, 7, 48, 2, 49, 7, 49, 2, 50, 7, 50, 2, 51, 7, 51, 2, 52, 7, 52, 2, 53, 7, 53, 2, 54, 7, 54, 2, 55, 7, 55, 1, 0, 1, 0, 5, 0, 118, 8, 0, 10, 0, 12, 0, 121, 9, 0, 1, 0, 4, 0, 124, 8, 0, 11, 0, 12, 0, 125, 1, 0, 5, 0, 129, 8, 0, 10, 0, 12, 0, 132, 9, 0, 1, 0, 1, 0, 5, 0, 136, 8, 0, 10, 0, 12, 0, 139, 9, 0, 1, 0, 5, 0, 142, 8, 0, 10, 0, 12, 0, 145, 9, 0, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 5, 1, 152, 8, 1, 10, 1, 12, 1, 155, 9, 1, 1, 1, 1, 1, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 4, 1, 4, 1, 4, 1, 4, 1, 4, 1, 4, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 1, 6, 1, 6, 1, 6, 1, 6, 1, 6, 1, 6, 1, 6, 1, 6, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 8, 1, 8, 1, 8, 1, 8, 1, 8, 1, 8, 1, 9, 1, 9, 1, 9, 1, 9, 1, 9, 1, 9, 1, 10, 1, 10, 1, 10, 1, 10, 1, 10, 1, 10, 1, 10, 1, 11, 1, 11, 1, 11, 1, 11, 1, 11, 1, 11, 1, 11, 1, 11, 1, 11, 1, 11, 1, 11, 1, 11, 1, 11, 1, 12, 1, 12, 1, 12, 1, 12, 1, 12, 1, 12, 1, 12, 1, 12, 1, 12, 1, 13, 1, 13, 1, 13, 1, 13, 1, 13, 1, 13, 1, 13, 1, 14, 1, 14, 1, 14, 1, 14, 1, 14, 1, 14, 1, 14, 1, 14, 1, 14, 1, 14, 1, 15, 1, 15, 1, 15, 1, 15, 1, 15, 1, 15, 1, 16, 1, 16, 1, 16, 1, 16, 1, 16, 1, 16, 1, 16, 1, 16, 1, 16, 1, 16, 1, 17, 1, 17, 1, 17, 1, 17, 1, 17, 1, 17, 1, 17, 1, 17, 1, 17, 1, 17, 1, 17, 1, 17, 1, 17, 1, 18, 1, 18, 1, 18, 1, 18, 1, 18, 1, 18, 1, 18, 1, 18, 1, 18, 1, 18, 1, 18, 1, 18, 1, 18, 1, 18, 1, 19, 1, 19, 1, 19, 1, 19, 1, 19, 1, 19, 1, 19, 1, 19, 1, 20, 1, 20, 1, 20, 1, 20, 1, 20, 1, 20, 1, 20, 1, 20, 1, 20, 1, 20, 1, 20, 1, 20, 1, 20, 1, 21, 1, 21, 1, 21, 1, 21, 1, 21, 1, 22, 1, 22, 1, 22, 1, 22, 3, 22, 333, 8, 22, 1, 22, 1, 22, 5, 22, 337, 8, 22, 10, 22, 12, 22, 340, 9, 22, 1, 22, 1, 22, 1, 22, 1, 22, 1, 23, 1, 23, 5, 23, 348, 8, 23, 10, 23, 12, 23, 351, 9, 23, 1, 23, 1, 23, 1, 23, 1, 23, 1, 24, 1, 24, 1, 24, 1, 25, 1, 25, 1, 25, 1, 26, 1, 26, 1, 26, 1, 27, 1, 27, 1, 27, 1, 28, 1, 28, 1, 28, 1, 28, 1, 28, 5, 28, 374, 8, 28, 10, 28, 12, 28, 377, 9, 28, 1, 28, 1, 28, 4, 28, 381, 8, 28, 11, 28, 12, 28, 382, 3, 28, 385, 8, 28, 1, 28, 1, 28, 1, 29, 1, 29, 1, 29, 1, 29, 1, 29, 1, 30, 1, 30, 1, 31, 1, 31, 1, 31, 1, 32, 1, 32, 1, 32, 1, 32, 1, 32, 5, 32, 404, 8, 32, 10, 32, 12, 32, 407, 9, 32, 1, 32, 1, 32, 1, 32, 1, 33, 1, 33, 1, 33, 1, 33, 5, 33, 416, 8, 33, 10, 33, 12, 33, 419, 9, 33, 1, 33, 1, 33, 1, 33, 1, 34, 1, 34, 5, 34, 426, 8, 34, 10, 34, 12, 34, 429, 9, 34, 1, 34, 4, 34, 432, 8, 34, 11, 34, 12, 34, 433, 1, 35, 1, 35, 1, 35, 1, 36, 1, 36, 1, 37, 1, 37, 1, 37, 1, 37, 5, 37, 445, 8, 37, 10, 37, 12, 37, 448, 9, 37, 1, 37, 1, 37, 1, 37, 1, 37, 1, 37, 3, 37, 455, 8, 37, 1, 37, 5, 37, 458, 8, 37, 10, 37, 12, 37, 461, 9, 37, 1, 37, 1, 37, 1, 37, 1, 37, 5, 37, 467, 8, 37, 10, 37, 12, 37, 470, 9, 37, 3, 37, 472, 8, 37, 1, 37, 1, 37, 1, 38, 1, 38, 1, 38, 1, 38, 1, 39, 1, 39, 1, 39, 1, 39, 1, 39, 1, 39, 1, 39, 5, 39, 487, 8, 39, 10, 39, 12, 39, 490, 9, 39, 1, 39, 5, 39, 493, 8, 39, 10, 39, 12, 39, 496, 9, 39, 1, 39, 1, 39, 1, 39, 1, 40, 1, 40, 3, 40, 503, 8, 40, 1, 41, 1, 41, 1, 41, 5, 41, 508, 8, 41, 10, 41, 12, 41, 511, 9, 41, 1, 41, 1, 41, 1, 41, 1, 42, 1, 42, 1, 42, 5, 42, 519, 8, 42, 10, 42, 12, 42, 522, 9, 42, 1, 42, 1, 42, 1, 42, 1, 42, 5, 42, 528, 8, 42, 10, 42, 12, 42, 531, 9, 42, 1, 42, 1, 42, 1, 42, 1, 42, 1, 42, 5, 42, 538, 8, 42, 10, 42, 12, 42, 541, 9, 42, 1, 42, 1, 42, 1, 42, 1, 42, 5, 42, 547, 8, 42, 10, 42, 12, 42, 550, 9, 42, 3, 42, 552, 8, 42, 1, 42, 1, 42, 1, 43, 4, 43, 557, 8, 43, 11, 43, 12, 43, 558, 1, 43, 1, 43, 1, 44, 1, 44, 1, 45, 4, 45, 566, 8, 45, 11, 45, 12, 45, 567, 1, 45, 1, 45, 1, 45, 1, 45, 1, 46, 1, 46, 1, 47, 1, 47, 5, 47, 578, 8, 47, 10, 47, 12, 47, 581, 9, 47, 1, 47, 1, 47, 1, 47, 1, 47, 1, 48, 1, 48, 1, 48, 1, 48, 1, 48, 1, 49, 4, 49, 593, 8, 49, 11, 49, 12, 49, 594, 1, 49, 1, 49, 1, 50, 1, 50, 1, 50, 1, 50, 5, 50, 603, 8, 50, 10, 50, 12, 50, 606, 9, 50, 1, 50, 1, 50, 1, 50, 1, 50, 1, 50, 1, 51, 1, 51, 1, 51, 3, 51, 616, 8, 51, 1, 51, 5, 51, 619, 8, 51, 10, 51, 12, 51, 622, 9, 51, 1, 51, 3, 51, 625, 8, 51, 1, 51, 1, 51, 1, 52, 1, 52, 1, 52, 1, 52, 3, 52, 633, 8, 52, 1, 52, 1, 52, 5, 52, 637, 8, 52, 10, 52, 12, 52, 640, 9, 52, 1, 52, 1, 52, 1, 52, 1, 52, 1, 53, 4, 53, 647, 8, 53, 11, 53, 12, 53, 648, 1, 53, 1, 53, 1, 53, 5, 53, 654, 8, 53, 10, 53, 12, 53, 657, 9, 53, 1, 53, 3, 53, 660, 8, 53, 1, 53, 1, 53, 1, 54, 1, 54, 1, 54, 1, 54, 1, 55, 4, 55, 669, 8, 55, 11, 55, 12, 55, 670, 1, 55, 1, 55, 1, 604, 0, 56, 3, 1, 5, 2, 7, 3, 9, 4, 11, 5, 13, 6, 15, 7, 17, 8, 19, 9, 21, 10, 23, 11, 25, 12, 27, 13, 29, 14, 31, 15, 33, 16, 35, 17, 37, 18, 39, 19, 41, 20, 43, 21, 45, 22, 47, 23, 49, 24, 51, 25, 53, 26, 55, 27, 57, 28, 59, 29, 61, 30, 63, 0, 65, 0, 67, 31, 69, 32, 71, 0, 73, 0, 75, 0, 77, 33, 79, 34, 81, 35, 83, 0, 85, 36, 87, 37, 89, 38, 91, 0, 93, 39, 95, 0, 97, 40, 99, 0, 101, 41, 103, 42, 105, 43, 107, 0, 109, 0, 111, 45, 113, 44, 3, 0, 1, 2, 45, 3, 0, 65, 90, 95, 95, 97, 122, 2, 0, 10, 10, 13, 13, 2, 0, 70, 70, 102, 102, 2, 0, 82, 82, 114, 114, 2, 0, 79, 79, 111, 111, 2, 0, 77, 77, 109, 109, 2, 0, 85, 85, 117, 117, 2, 0, 78, 78, 110, 110, 2, 0, 67, 67, 99, 99, 2, 0, 68, 68, 100, 100, 2, 0, 69, 69, 101, 101, 2, 0, 76, 76, 108, 108, 2, 0, 65, 65, 97, 97, 2, 0, 66, 66, 98, 98, 2, 0, 88, 88, 120, 120, 2, 0, 80, 80, 112, 112, 2, 0, 83, 83, 115, 115, 2, 0, 86, 86, 118, 118, 2, 0, 89, 89, 121, 121, 2, 0, 84, 84, 116, 116, 2, 0, 73, 73, 105, 105, 2, 0, 87, 87, 119, 119, 2, 0, 75, 75, 107, 107, 2, 0, 71, 71, 103, 103, 2, 0, 72, 72, 104, 104, 4, 0, 48, 57, 65, 90, 95, 95, 97, 122, 2, 0, 92, 92, 96, 96, 2, 0, 9, 9, 32, 32, 2, 0, 65, 90, 97, 122, 5, 0, 45, 45, 48, 57, 65, 90, 95, 95, 97, 122, 3, 0, 9, 10, 13, 13, 32, 32, 8, 0, 9, 10, 13, 13, 32, 32, 34, 34, 36, 36, 39, 39, 60, 61, 91, 93, 3, 0, 10, 10, 13, 13, 96, 96, 5, 0, 10, 10, 13, 13, 34, 34, 92, 92, 96, 96, 3, 0, 10, 10, 13, 13, 39, 39, 3, 0, 48, 57, 65, 70, 97, 102, 1, 0, 125, 125, 5, 0, 33, 33, 35, 36, 42, 42, 48, 57, 63, 64, 1, 0, 40, 41, 4, 0, 9, 10, 13, 13, 32, 32, 96, 96, 9, 0, 9, 10, 13, 13, 32, 32, 34, 34, 36, 36, 39, 39, 45, 45, 60, 61, 91, 93, 3, 0, 9, 9, 12, 13, 32, 32, 6, 0, 9, 10, 13, 13, 32, 32, 60, 60, 92, 92, 96, 96, 4, 0, 9, 10, 13, 13, 32, 32, 60, 60, 1, 0, 10, 10, 725, 0, 3, 1, 0, 0, 0, 0, 5, 1, 0, 0, 0, 0, 7, 1, 0, 0, 0, 0, 9, 1, 0, 0, 0, 0, 11, 1, 0, 0, 0, 0, 13, 1, 0, 0, 0, 0, 15, 1, 0, 0, 0, 0, 17, 1, 0, 0, 0, 0, 19, 1, 0, 0, 0, 0, 21, 1, 0, 0, 0, 0, 23, 1, 0, 0, 0, 0, 25, 1, 0, 0, 0, 0, 27, 1, 0, 0, 0, 0, 29, 1, 0, 0, 0, 0, 31, 1, 0, 0, 0, 0, 33, 1, 0, 0, 0, 0, 35, 1, 0, 0, 0, 0, 37, 1, 0, 0, 0, 0, 39, 1, 0, 0, 0, 0, 41, 1, 0, 0, 0, 0, 43, 1, 0, 0, 0, 0, 45, 1, 0, 0, 0, 0, 47, 1, 0, 0, 0, 0, 49, 1, 0, 0, 0, 0, 51, 1, 0, 0, 0, 0, 53, 1, 0, 0, 0, 0, 55, 1, 0, 0, 0, 0, 57, 1, 0, 0, 0, 0, 59, 1, 0, 0, 0, 0, 61, 1, 0, 0, 0, 0, 67, 1, 0, 0, 0, 0, 69, 1, 0, 0, 0, 0, 77, 1, 0, 0, 0, 0, 79, 1, 0, 0, 0, 0, 81, 1, 0, 0, 0, 0, 85, 1, 0, 0, 0, 0, 87, 1, 0, 0, 0, 0, 89, 1, 0, 0, 0, 0, 93, 1, 0, 0, 0, 1, 97, 1, 0, 0, 0, 1, 99, 1, 0, 0, 0, 1, 101, 1, 0, 0, 0, 1, 103, 1, 0, 0, 0, 1, 105, 1, 0, 0, 0, 1, 107, 1, 0, 0, 0, 1, 109, 1, 0, 0, 0, 2, 111, 1, 0, 0, 0, 2, 113, 1, 0, 0, 0, 3, 115, 1, 0, 0, 0, 5, 149, 1, 0, 0, 0, 7, 158, 1, 0, 0, 0, 9, 165, 1, 0, 0, 0, 11, 171, 1, 0, 0, 0, 13, 177, 1, 0, 0, 0, 15, 184, 1, 0, 0, 0, 17, 192, 1, 0, 0, 0, 19, 201, 1, 0, 0, 0, 21, 207, 1, 0, 0, 0, 23, 213, 1, 0, 0, 0, 25, 220, 1, 0, 0, 0, 27, 233, 1, 0, 0, 0, 29, 242, 1, 0, 0, 0, 31, 249, 1, 0, 0, 0, 33, 259, 1, 0, 0, 0, 35, 265, 1, 0, 0, 0, 37, 275, 1, 0, 0, 0, 39, 288, 1, 0, 0, 0, 41, 302, 1, 0, 0, 0, 43, 310, 1, 0, 0, 0, 45, 323, 1, 0, 0, 0, 47, 328, 1, 0, 0, 0, 49, 345, 1, 0, 0, 0, 51, 356, 1, 0, 0, 0, 53, 359, 1, 0, 0, 0, 55, 362, 1, 0, 0, 0, 57, 365, 1, 0, 0, 0, 59, 368, 1, 0, 0, 0, 61, 388, 1, 0, 0, 0, 63, 393, 1, 0, 0, 0, 65, 395, 1, 0, 0, 0, 67, 398, 1, 0, 0, 0, 69, 411, 1, 0, 0, 0, 71, 423, 1, 0, 0, 0, 73, 435, 1, 0, 0, 0, 75, 438, 1, 0, 0, 0, 77, 471, 1, 0, 0, 0, 79, 475, 1, 0, 0, 0, 81, 479, 1, 0, 0, 0, 83, 502, 1, 0, 0, 0, 85, 504, 1, 0, 0, 0, 87, 551, 1, 0, 0, 0, 89, 556, 1, 0, 0, 0, 91, 562, 1, 0, 0, 0, 93, 565, 1, 0, 0, 0, 95, 573, 1, 0, 0, 0, 97, 575, 1, 0, 0, 0, 99, 586, 1, 0, 0, 0, 101, 592, 1, 0, 0, 0, 103, 598, 1, 0, 0, 0, 105, 615, 1, 0, 0, 0, 107, 628, 1, 0, 0, 0, 109, 659, 1, 0, 0, 0, 111, 663, 1, 0, 0, 0, 113, 668, 1, 0, 0, 0, 115, 119, 5, 35, 0, 0, 116, 118, 3, 91, 44, 0, 117, 116, 1, 0, 0, 0, 118, 121, 1, 0, 0, 0, 119, 117, 1, 0, 0, 0, 119, 120, 1, 0, 0, 0, 120, 123, 1, 0, 0, 0, 121, 119, 1, 0, 0, 0, 122, 124, 7, 0, 0, 0, 123, 122, 1, 0, 0, 0, 124, 125, 1, 0, 0, 0, 125, 123, 1, 0, 0, 0, 125, 126, 1, 0, 0, 0, 126, 130, 1, 0, 0, 0, 127, 129, 3, 91, 44, 0, 128, 127, 1, 0, 0, 0, 129, 132, 1, 0, 0, 0, 130, 128, 1, 0, 0, 0, 130, 131, 1, 0, 0, 0, 131, 133, 1, 0, 0, 0, 132, 130, 1, 0, 0, 0, 133, 137, 5, 61, 0, 0, 134, 136, 3, 91, 44, 0, 135, 134, 1, 0, 0, 0, 136, 139, 1, 0, 0, 0, 137, 135, 1, 0, 0, 0, 137, 138, 1, 0, 0, 0, 138, 143, 1, 0, 0, 0, 139, 137, 1, 0, 0, 0, 140, 142, 8, 1, 0, 0, 141, 140, 1, 0, 0, 0, 142, 145, 1, 0, 0, 0, 143, 141, 1, 0, 0, 0, 143, 144, 1, 0, 0, 0, 144, 146, 1, 0, 0, 0, 145, 143, 1, 0, 0, 0, 146, 147, 3, 95, 46, 0, 147, 148, 6, 0, 0, 0, 148, 4, 1, 0, 0, 0, 149, 153, 5, 35, 0, 0, 150, 152, 8, 1, 0, 0, 151, 150, 1, 0, 0, 0, 152, 155, 1, 0, 0, 0, 153, 151, 1, 0, 0, 0, 153, 154, 1, 0, 0, 0, 154, 156, 1, 0, 0, 0, 155, 153, 1, 0, 0, 0, 156, 157, 6, 1, 1, 0, 157, 6, 1, 0, 0, 0, 158, 159, 7, 2, 0, 0, 159, 160, 7, 3, 0, 0, 160, 161, 7, 4, 0, 0, 161, 162, 7, 5, 0, 0, 162, 163, 1, 0, 0, 0, 163, 164, 6, 2, 2, 0, 164, 8, 1, 0, 0, 0, 165, 166, 7, 3, 0, 0, 166, 167, 7, 6, 0, 0, 167, 168, 7, 7, 0, 0, 168, 169, 1, 0, 0, 0, 169, 170, 6, 3, 3, 0, 170, 10, 1, 0, 0, 0, 171, 172, 7, 8, 0, 0, 172, 173, 7, 5, 0, 0, 173, 174, 7, 9, 0, 0, 174, 175, 1, 0, 0, 0, 175, 176, 6, 4, 4, 0, 176, 12, 1, 0, 0, 0, 177, 178, 7, 7, 0, 0, 178, 179, 7, 4, 0, 0, 179, 180, 7, 7, 0, 0, 180, 181, 7, 10, 0, 0, 181, 182, 1, 0, 0, 0, 182, 183, 6, 5, 5, 0, 183, 14, 1, 0, 0, 0, 184, 185, 7, 11, 0, 0, 185, 186, 7, 12, 0, 0, 186, 187, 7, 13, 0, 0, 187, 188, 7, 10, 0, 0, 188, 189, 7, 11, 0, 0, 189, 190, 1, 0, 0, 0, 190, 191, 6, 6, 6, 0, 191, 16, 1, 0, 0, 0, 192, 193, 7, 10, 0, 0, 193, 194, 7, 14, 0, 0, 194, 195, 7, 15, 0, 0, 195, 196, 7, 4, 0, 0, 196, 197, 7, 16, 0, 0, 197, 198, 7, 10, 0, 0, 198, 199, 1, 0, 0, 0, 199, 200, 6, 7, 7, 0, 200, 18, 1, 0, 0, 0, 201, 202, 7, 10, 0, 0, 202, 203, 7, 7, 0, 0, 203, 204, 7, 17, 0, 0, 204, 205, 1, 0, 0, 0, 205, 206, 6, 8, 8, 0, 206, 20, 1, 0, 0, 0, 207, 208, 7, 12, 0, 0, 208, 209, 7, 9, 0, 0, 209, 210, 7, 9, 0, 0, 210, 211, 1, 0, 0, 0, 211, 212, 6, 9, 9, 0, 212, 22, 1, 0, 0, 0, 213, 214, 7, 8, 0, 0, 214, 215, 7, 4, 0, 0, 215, 216, 7, 15, 0, 0, 216, 217, 7, 18, 0, 0, 217, 218, 1, 0, 0, 0, 218, 219, 6, 10, 10, 0, 219, 24, 1, 0, 0, 0, 220, 221, 7, 10, 0, 0, 221, 222, 7, 7, 0, 0, 222, 223, 7, 19, 0, 0, 223, 224, 7, 3, 0, 0, 224, 225, 7, 18, 0, 0, 225, 226, 7, 15, 0, 0, 226, 227, 7, 4, 0, 0, 227, 228, 7, 20, 0, 0, 228, 229, 7, 7, 0, 0, 229, 230, 7, 19, 0, 0, 230, 231, 1, 0, 0, 0, 231, 232, 6, 11, 11, 0, 232, 26, 1, 0, 0, 0, 233, 234, 7, 17, 0, 0, 234, 235, 7, 4, 0, 0, 235, 236, 7, 11, 0, 0, 236, 237, 7, 6, 0, 0, 237, 238, 7, 5, 0, 0, 238, 239, 7, 10, 0, 0, 239, 240, 1, 0, 0, 0, 240, 241, 6, 12, 12, 0, 241, 28, 1, 0, 0, 0, 242, 243, 7, 6, 0, 0, 243, 244, 7, 16, 0, 0, 244, 245, 7, 10, 0, 0, 245, 246, 7, 3, 0, 0, 246, 247, 1, 0, 0, 0, 247, 248, 6, 13, 13, 0, 248, 30, 1, 0, 0, 0, 249, 250, 7, 21, 0, 0, 250, 251, 7, 4, 0, 0, 251, 252, 7, 3, 0, 0, 252, 253, 7, 22, 0, 0, 253, 254, 7, 9, 0, 0, 254, 255, 7, 20, 0, 0, 255, 256, 7, 3, 0, 0, 256, 257, 1, 0, 0, 0, 257, 258, 6, 14, 14, 0, 258, 32, 1, 0, 0, 0, 259, 260, 7, 12, 0, 0, 260, 261, 7, 3, 0, 0, 261, 262, 7, 23, 0, 0, 262, 263, 1, 0, 0, 0, 263, 264, 6, 15, 15, 0, 264, 34, 1, 0, 0, 0, 265, 266, 7, 4, 0, 0, 266, 267, 7, 7, 0, 0, 267, 268, 7, 13, 0, 0, 268, 269, 7, 6, 0, 0, 269, 270, 7, 20, 0, 0, 270, 271, 7, 11, 0, 0, 271, 272, 7, 9, 0, 0, 272, 273, 1, 0, 0, 0, 273, 274, 6, 16, 16, 0, 274, 36, 1, 0, 0, 0, 275, 276, 7, 16, 0, 0, 276, 277, 7, 19, 0, 0, 277, 278, 7, 4, 0, 0, 278, 279, 7, 15, 0, 0, 279, 280, 7, 16, 0, 0, 280, 281, 7, 20, 0, 0, 281, 282, 7, 23, 0, 0, 282, 283, 7, 7, 0, 0, 283, 284, 7, 12, 0, 0, 284, 285, 7, 11, 0, 0, 285, 286, 1, 0, 0, 0, 286, 287, 6, 17, 17, 0, 287, 38, 1, 0, 0, 0, 288, 289, 7, 24, 0, 0, 289, 290, 7, 10, 0, 0, 290, 291, 7, 12, 0, 0, 291, 292, 7, 11, 0, 0, 292, 293, 7, 19, 0, 0, 293, 294, 7, 24, 0, 0, 294, 295, 7, 8, 0, 0, 295, 296, 7, 24, 0, 0, 296, 297, 7, 10, 0, 0, 297, 298, 7, 8, 0, 0, 298, 299, 7, 22, 0, 0, 299, 300, 1, 0, 0, 0, 300, 301, 6, 18, 18, 0, 301, 40, 1, 0, 0, 0, 302, 303, 7, 16, 0, 0, 303, 304, 7, 24, 0, 0, 304, 305, 7, 10, 0, 0, 305, 306, 7, 11, 0, 0, 306, 307, 7, 11, 0, 0, 307, 308, 1, 0, 0, 0, 308, 309, 6, 19, 19, 0, 309, 42, 1, 0, 0, 0, 310, 311, 7, 5, 0, 0, 311, 312, 7, 12, 0, 0, 312, 313, 7, 20, 0, 0, 313, 314, 7, 7, 0, 0, 314, 315, 7, 19, 0, 0, 315, 316, 7, 12, 0, 0, 316, 317, 7, 20, 0, 0, 317, 318, 7, 7, 0, 0, 318, 319, 7, 10, 0, 0, 319, 320, 7, 3, 0, 0, 320, 321, 1, 0, 0, 0, 321, 322, 6, 20, 20, 0, 322, 44, 1, 0, 0, 0, 323, 324, 7, 12, 0, 0, 324, 325, 7, 16, 0, 0, 325, 326, 1, 0, 0, 0, 326, 327, 6, 21, 21, 0, 327, 46, 1, 0, 0, 0, 328, 329, 5, 60, 0, 0, 329, 330, 5, 60, 0, 0, 330, 332, 1, 0, 0, 0, 331, 333, 5, 45, 0, 0, 332, 331, 1, 0, 0, 0, 332, 333, 1, 0, 0, 0, 333, 334, 1, 0, 0, 0, 334, 338, 7, 0, 0, 0, 335, 337, 7, 25, 0, 0, 336, 335, 1, 0, 0, 0, 337, 340, 1, 0, 0, 0, 338, 336, 1, 0, 0, 0, 338, 339, 1, 0, 0, 0, 339, 341, 1, 0, 0, 0, 340, 338, 1, 0, 0, 0, 341, 342, 6, 22, 22, 0, 342, 343, 1, 0, 0, 0, 343, 344, 6, 22, 23, 0, 344, 48, 1, 0, 0, 0, 345, 349, 7, 26, 0, 0, 346, 348, 7, 27, 0, 0, 347, 346, 1, 0, 0, 0, 348, 351, 1, 0, 0, 0, 349, 347, 1, 0, 0, 0, 349, 350, 1, 0, 0, 0, 350, 352, 1, 0, 0, 0, 351, 349, 1, 0, 0, 0, 352, 353, 3, 95, 46, 0, 353, 354, 1, 0, 0, 0, 354, 355, 6, 23, 1, 0, 355, 50, 1, 0, 0, 0, 356, 357, 5, 91, 0, 0, 357, 358, 6, 24, 24, 0, 358, 52, 1, 0, 0, 0, 359, 360, 5, 93, 0, 0, 360, 361, 6, 25, 25, 0, 361, 54, 1, 0, 0, 0, 362, 363, 5, 44, 0, 0, 363, 364, 6, 26, 26, 0, 364, 56, 1, 0, 0, 0, 365, 366, 5, 61, 0, 0, 366, 367, 6, 27, 27, 0, 367, 58, 1, 0, 0, 0, 368, 369, 5, 45, 0, 0, 369, 370, 5, 45, 0, 0, 370, 371, 1, 0, 0, 0, 371, 375, 7, 28, 0, 0, 372, 374, 7, 29, 0, 0, 373, 372, 1, 0, 0, 0, 374, 377, 1, 0, 0, 0, 375, 373, 1, 0, 0, 0, 375, 376, 1, 0, 0, 0, 376, 384, 1, 0, 0, 0, 377, 375, 1, 0, 0, 0, 378, 380, 5, 61, 0, 0, 379, 381, 8, 30, 0, 0, 380, 379, 1, 0, 0, 0, 381, 382, 1, 0, 0, 0, 382, 380, 1, 0, 0, 0, 382, 383, 1, 0, 0, 0, 383, 385, 1, 0, 0, 0, 384, 378, 1, 0, 0, 0, 384, 385, 1, 0, 0, 0, 385, 386, 1, 0, 0, 0, 386, 387, 6, 28, 28, 0, 387, 60, 1, 0, 0, 0, 388, 389, 5, 45, 0, 0, 389, 390, 5, 45, 0, 0, 390, 391, 1, 0, 0, 0, 391, 392, 6, 29, 29, 0, 392, 62, 1, 0, 0, 0, 393, 394, 8, 31, 0, 0, 394, 64, 1, 0, 0, 0, 395, 396, 5, 92, 0, 0, 396, 397, 9, 0, 0, 0, 397, 66, 1, 0, 0, 0, 398, 405, 5, 34, 0, 0, 399, 404, 3, 73, 35, 0, 400, 404, 3, 71, 34, 0, 401, 404, 7, 32, 0, 0, 402, 404, 8, 33, 0, 0, 403, 399, 1, 0, 0, 0, 403, 400, 1, 0, 0, 0, 403, 401, 1, 0, 0, 0, 403, 402, 1, 0, 0, 0, 404, 407, 1, 0, 0, 0, 405, 403, 1, 0, 0, 0, 405, 406, 1, 0, 0, 0, 406, 408, 1, 0, 0, 0, 407, 405, 1, 0, 0, 0, 408, 409, 5, 34, 0, 0, 409, 410, 6, 32, 30, 0, 410, 68, 1, 0, 0, 0, 411, 417, 5, 39, 0, 0, 412, 416, 3, 71, 34, 0, 413, 416, 7, 1, 0, 0, 414, 416, 8, 34, 0, 0, 415, 412, 1, 0, 0, 0, 415, 413, 1, 0, 0, 0, 415, 414, 1, 0, 0, 0, 416, 419, 1, 0, 0, 0, 417, 415, 1, 0, 0, 0, 417, 418, 1, 0, 0, 0, 418, 420, 1, 0, 0, 0, 419, 417, 1, 0, 0, 0, 420, 421, 5, 39, 0, 0, 421, 422, 6, 33, 31, 0, 422, 70, 1, 0, 0, 0, 423, 427, 7, 26, 0, 0, 424, 426, 7, 27, 0, 0, 425, 424, 1, 0, 0, 0, 426, 429, 1, 0, 0, 0, 427, 425, 1, 0, 0, 0, 427, 428, 1, 0, 0, 0, 428, 431, 1, 0, 0, 0, 429, 427, 1, 0, 0, 0, 430, 432, 7, 1, 0, 0, 431, 430, 1, 0, 0, 0, 432, 433, 1, 0, 0, 0, 433, 431, 1, 0, 0, 0, 433, 434, 1, 0, 0, 0, 434, 72, 1, 0, 0, 0, 435, 436, 5, 92, 0, 0, 436, 437, 8, 1, 0, 0, 437, 74, 1, 0, 0, 0, 438, 439, 7, 35, 0, 0, 439, 76, 1, 0, 0, 0, 440, 441, 5, 36, 0, 0, 441, 442, 5, 123, 0, 0, 442, 446, 7, 0, 0, 0, 443, 445, 7, 25, 0, 0, 444, 443, 1, 0, 0, 0, 445, 448, 1, 0, 0, 0, 446, 444, 1, 0, 0, 0, 446, 447, 1, 0, 0, 0, 447, 454, 1, 0, 0, 0, 448, 446, 1, 0, 0, 0, 449, 450, 5, 58, 0, 0, 450, 455, 5, 45, 0, 0, 451, 452, 5, 58, 0, 0, 452, 455, 5, 43, 0, 0, 453, 455, 5, 58, 0, 0, 454, 449, 1, 0, 0, 0, 454, 451, 1, 0, 0, 0, 454, 453, 1, 0, 0, 0, 454, 455, 1, 0, 0, 0, 455, 459, 1, 0, 0, 0, 456, 458, 8, 36, 0, 0, 457, 456, 1, 0, 0, 0, 458, 461, 1, 0, 0, 0, 459, 457, 1, 0, 0, 0, 459, 460, 1, 0, 0, 0, 460, 462, 1, 0, 0, 0, 461, 459, 1, 0, 0, 0, 462, 472, 5, 125, 0, 0, 463, 464, 5, 36, 0, 0, 464, 468, 7, 0, 0, 0, 465, 467, 7, 25, 0, 0, 466, 465, 1, 0, 0, 0, 467, 470, 1, 0, 0, 0, 468, 466, 1, 0, 0, 0, 468, 469, 1, 0, 0, 0, 469, 472, 1, 0, 0, 0, 470, 468, 1, 0, 0, 0, 471, 440, 1, 0, 0, 0, 471, 463, 1, 0, 0, 0, 472, 473, 1, 0, 0, 0, 473, 474, 6, 37, 32, 0, 474, 78, 1, 0, 0, 0, 475, 476, 5, 36, 0, 0, 476, 477, 7, 37, 0, 0, 477, 478, 6, 38, 33, 0, 478, 80, 1, 0, 0, 0, 479, 480, 5, 36, 0, 0, 480, 481, 5, 40, 0, 0, 481, 494, 1, 0, 0, 0, 482, 493, 3, 81, 39, 0, 483, 493, 8, 38, 0, 0, 484, 488, 5, 40, 0, 0, 485, 487, 3, 83, 40, 0, 486, 485, 1, 0, 0, 0, 487, 490, 1, 0, 0, 0, 488, 486, 1, 0, 0, 0, 488, 489, 1, 0, 0, 0, 489, 491, 1, 0, 0, 0, 490, 488, 1, 0, 0, 0, 491, 493, 5, 41, 0, 0, 492, 482, 1, 0, 0, 0, 492, 483, 1, 0, 0, 0, 492, 484, 1, 0, 0, 0, 493, 496, 1, 0, 0, 0, 494, 492, 1, 0, 0, 0, 494, 495, 1, 0, 0, 0, 495, 497, 1, 0, 0, 0, 496, 494, 1, 0, 0, 0, 497, 498, 5, 41, 0, 0, 498, 499, 6, 39, 34, 0, 499, 82, 1, 0, 0, 0, 500, 503, 3, 81, 39, 0, 501, 503, 8, 38, 0, 0, 502, 500, 1, 0, 0, 0, 502, 501, 1, 0, 0, 0, 503, 84, 1, 0, 0, 0, 504, 505, 5, 96, 0, 0, 505, 509, 8, 39, 0, 0, 506, 508, 8, 32, 0, 0, 507, 506, 1, 0, 0, 0, 508, 511, 1, 0, 0, 0, 509, 507, 1, 0, 0, 0, 509, 510, 1, 0, 0, 0, 510, 512, 1, 0, 0, 0, 511, 509, 1, 0, 0, 0, 512, 513, 5, 96, 0, 0, 513, 514, 6, 41, 35, 0, 514, 86, 1, 0, 0, 0, 515, 520, 8, 40, 0, 0, 516, 519, 3, 63, 30, 0, 517, 519, 3, 65, 31, 0, 518, 516, 1, 0, 0, 0, 518, 517, 1, 0, 0, 0, 519, 522, 1, 0, 0, 0, 520, 518, 1, 0, 0, 0, 520, 521, 1, 0, 0, 0, 521, 552, 1, 0, 0, 0, 522, 520, 1, 0, 0, 0, 523, 524, 5, 45, 0, 0, 524, 529, 8, 40, 0, 0, 525, 528, 3, 63, 30, 0, 526, 528, 3, 65, 31, 0, 527, 525, 1, 0, 0, 0, 527, 526, 1, 0, 0, 0, 528, 531, 1, 0, 0, 0, 529, 527, 1, 0, 0, 0, 529, 530, 1, 0, 0, 0, 530, 552, 1, 0, 0, 0, 531, 529, 1, 0, 0, 0, 532, 552, 5, 45, 0, 0, 533, 534, 5, 60, 0, 0, 534, 539, 8, 31, 0, 0, 535, 538, 3, 63, 30, 0, 536, 538, 3, 65, 31, 0, 537, 535, 1, 0, 0, 0, 537, 536, 1, 0, 0, 0, 538, 541, 1, 0, 0, 0, 539, 537, 1, 0, 0, 0, 539, 540, 1, 0, 0, 0, 540, 552, 1, 0, 0, 0, 541, 539, 1, 0, 0, 0, 542, 552, 5, 60, 0, 0, 543, 548, 3, 65, 31, 0, 544, 547, 3, 63, 30, 0, 545, 547, 3, 65, 31, 0, 546, 544, 1, 0, 0, 0, 546, 545, 1, 0, 0, 0, 547, 550, 1, 0, 0, 0, 548, 546, 1, 0, 0, 0, 548, 549, 1, 0, 0, 0, 549, 552, 1, 0, 0, 0, 550, 548, 1, 0, 0, 0, 551, 515, 1, 0, 0, 0, 551, 523, 1, 0, 0, 0, 551, 532, 1, 0, 0, 0, 551, 533, 1, 0, 0, 0, 551, 542, 1, 0, 0, 0, 551, 543, 1, 0, 0, 0, 552, 553, 1, 0, 0, 0, 553, 554, 6, 42, 36, 0, 554, 88, 1, 0, 0, 0, 555, 557, 3, 91, 44, 0, 556, 555, 1, 0, 0, 0, 557, 558, 1, 0, 0, 0, 558, 556, 1, 0, 0, 0, 558, 559, 1, 0, 0, 0, 559, 560, 1, 0, 0, 0, 560, 561, 6, 43, 1, 0, 561, 90, 1, 0, 0, 0, 562, 563, 7, 27, 0, 0, 563, 92, 1, 0, 0, 0, 564, 566, 3, 95, 46, 0, 565, 564, 1, 0, 0, 0, 566, 567, 1, 0, 0, 0, 567, 565, 1, 0, 0, 0, 567, 568, 1, 0, 0, 0, 568, 569, 1, 0, 0, 0, 569, 570, 6, 45, 37, 0, 570, 571, 1, 0, 0, 0, 571, 572, 6, 45, 1, 0, 572, 94, 1, 0, 0, 0, 573, 574, 7, 1, 0, 0, 574, 96, 1, 0, 0, 0, 575, 579, 7, 26, 0, 0, 576, 578, 7, 27, 0, 0, 577, 576, 1, 0, 0, 0, 578, 581, 1, 0, 0, 0, 579, 577, 1, 0, 0, 0, 579, 580, 1, 0, 0, 0, 580, 582, 1, 0, 0, 0, 581, 579, 1, 0, 0, 0, 582, 583, 5, 10, 0, 0, 583, 584, 1, 0, 0, 0, 584, 585, 6, 47, 1, 0, 585, 98, 1, 0, 0, 0, 586, 587, 5, 10, 0, 0, 587, 588, 1, 0, 0, 0, 588, 589, 6, 48, 38, 0, 589, 590, 6, 48, 39, 0, 590, 100, 1, 0, 0, 0, 591, 593, 7, 41, 0, 0, 592, 591, 1, 0, 0, 0, 593, 594, 1, 0, 0, 0, 594, 592, 1, 0, 0, 0, 594, 595, 1, 0, 0, 0, 595, 596, 1, 0, 0, 0, 596, 597, 6, 49, 1, 0, 597, 102, 1, 0, 0, 0, 598, 599, 5, 47, 0, 0, 599, 600, 5, 42, 0, 0, 600, 604, 1, 0, 0, 0, 601, 603, 9, 0, 0, 0, 602, 601, 1, 0, 0, 0, 603, 606, 1, 0, 0, 0, 604, 605, 1, 0, 0, 0, 604, 602, 1, 0, 0, 0, 605, 607, 1, 0, 0, 0, 606, 604, 1, 0, 0, 0, 607, 608, 5, 42, 0, 0, 608, 609, 5, 47, 0, 0, 609, 610, 1, 0, 0, 0, 610, 611, 6, 50, 1, 0, 611, 104, 1, 0, 0, 0, 612, 613, 5, 47, 0, 0, 613, 616, 5, 47, 0, 0, 614, 616, 5, 35, 0, 0, 615, 612, 1, 0, 0, 0, 615, 614, 1, 0, 0, 0, 616, 620, 1, 0, 0, 0, 617, 619, 8, 1, 0, 0, 618, 617, 1, 0, 0, 0, 619, 622, 1, 0, 0, 0, 620, 618, 1, 0, 0, 0, 620, 621, 1, 0, 0, 0, 621, 624, 1, 0, 0, 0, 622, 620, 1, 0, 0, 0, 623, 625, 5, 13, 0, 0, 624, 623, 1, 0, 0, 0, 624, 625, 1, 0, 0, 0, 625, 626, 1, 0, 0, 0, 626, 627, 6, 51, 1, 0, 627, 106, 1, 0, 0, 0, 628, 629, 5, 60, 0, 0, 629, 630, 5, 60, 0, 0, 630, 632, 1, 0, 0, 0, 631, 633, 5, 45, 0, 0, 632, 631, 1, 0, 0, 0, 632, 633, 1, 0, 0, 0, 633, 634, 1, 0, 0, 0, 634, 638, 7, 0, 0, 0, 635, 637, 7, 25, 0, 0, 636, 635, 1, 0, 0, 0, 637, 640, 1, 0, 0, 0, 638, 636, 1, 0, 0, 0, 638, 639, 1, 0, 0, 0, 639, 641, 1, 0, 0, 0, 640, 638, 1, 0, 0, 0, 641, 642, 6, 52, 40, 0, 642, 643, 1, 0, 0, 0, 643, 644, 6, 52, 41, 0, 644, 108, 1, 0, 0, 0, 645, 647, 8, 42, 0, 0, 646, 645, 1, 0, 0, 0, 647, 648, 1, 0, 0, 0, 648, 646, 1, 0, 0, 0, 648, 649, 1, 0, 0, 0, 649, 660, 1, 0, 0, 0, 650, 651, 5, 60, 0, 0, 651, 655, 8, 43, 0, 0, 652, 654, 8, 30, 0, 0, 653, 652, 1, 0, 0, 0, 654, 657, 1, 0, 0, 0, 655, 653, 1, 0, 0, 0, 655, 656, 1, 0, 0, 0, 656, 660, 1, 0, 0, 0, 657, 655, 1, 0, 0, 0, 658, 660, 5, 60, 0, 0, 659, 646, 1, 0, 0, 0, 659, 650, 1, 0, 0, 0, 659, 658, 1, 0, 0, 0, 660, 661, 1, 0, 0, 0, 661, 662, 6, 53, 42, 0, 662, 110, 1, 0, 0, 0, 663, 664, 5, 10, 0, 0, 664, 665, 1, 0, 0, 0, 665, 666, 6, 54, 38, 0, 666, 112, 1, 0, 0, 0, 667, 669, 8, 44, 0, 0, 668, 667, 1, 0, 0, 0, 669, 670, 1, 0, 0, 0, 670, 668, 1, 0, 0, 0, 670, 671, 1, 0, 0, 0, 671, 672, 1, 0, 0, 0, 672, 673, 6, 55, 43, 0, 673, 114, 1, 0, 0, 0, 54, 0, 1, 2, 119, 125, 130, 137, 143, 153, 332, 338, 349, 375, 382, 384, 403, 405, 415, 417, 427, 433, 446, 454, 459, 468, 471, 488, 492, 494, 502, 509, 518, 520, 527, 529, 537, 539, 546, 548, 551, 558, 567, 579, 594, 604, 615, 620, 624, 632, 638, 648, 655, 659, 670, 44, 1, 0, 0, 0, 1, 0, 1, 2, 1, 1, 3, 2, 1, 4, 3, 1, 5, 4, 1, 6, 5, 1, 7, 6, 1, 8, 7, 1, 9, 8, 1, 10, 9, 1, 11, 10, 1, 12, 11, 1, 13, 12, 1, 14, 13, 1, 15, 14, 1, 16, 15, 1, 17, 16, 1, 18, 17, 1, 19, 18, 1, 20, 19, 1, 21, 20, 1, 22, 21, 5, 1, 0, 1, 24, 22, 1, 25, 23, 1, 26, 24, 1, 27, 25, 1, 28, 26, 1, 29, 27, 1, 32, 28, 1, 33, 29, 1, 37, 30, 1, 38, 31, 1, 39, 32, 1, 41, 33, 1, 42, 34, 1, 45, 35, 7, 39, 0, 2, 2, 0, 1, 52, 36, 7, 23, 0, 7, 37, 0, 1, 55, 37] \ No newline at end of file diff --git a/rewrite-docker/src/main/java/org/openrewrite/docker/internal/grammar/DockerLexer.java b/rewrite-docker/src/main/java/org/openrewrite/docker/internal/grammar/DockerLexer.java new file mode 100644 index 0000000000..0119c133a9 --- /dev/null +++ b/rewrite-docker/src/main/java/org/openrewrite/docker/internal/grammar/DockerLexer.java @@ -0,0 +1,1039 @@ +/* + * Copyright 2026 the original author or authors. + *

+ * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * https://www.apache.org/licenses/LICENSE-2.0 + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +// Generated from /home/tim/Documents/workspace/openrewrite/rewrite/rewrite-docker/src/main/antlr/DockerLexer.g4 by ANTLR 4.13.2 +package org.openrewrite.docker.internal.grammar; +import java.util.LinkedList; +import java.util.Queue; +import org.antlr.v4.runtime.Lexer; +import org.antlr.v4.runtime.CharStream; +import org.antlr.v4.runtime.Token; +import org.antlr.v4.runtime.TokenStream; +import org.antlr.v4.runtime.*; +import org.antlr.v4.runtime.atn.*; +import org.antlr.v4.runtime.dfa.DFA; +import org.antlr.v4.runtime.misc.*; + +@SuppressWarnings({"all", "warnings", "unchecked", "unused", "cast", "CheckReturnValue", "this-escape"}) +public class DockerLexer extends Lexer { + static { RuntimeMetaData.checkVersion("4.13.2", RuntimeMetaData.VERSION); } + + protected static final DFA[] _decisionToDFA; + protected static final PredictionContextCache _sharedContextCache = + new PredictionContextCache(); + public static final int + PARSER_DIRECTIVE=1, COMMENT=2, FROM=3, RUN=4, CMD=5, NONE=6, LABEL=7, + EXPOSE=8, ENV=9, ADD=10, COPY=11, ENTRYPOINT=12, VOLUME=13, USER=14, WORKDIR=15, + ARG=16, ONBUILD=17, STOPSIGNAL=18, HEALTHCHECK=19, SHELL=20, MAINTAINER=21, + AS=22, HEREDOC_START=23, LINE_CONTINUATION=24, LBRACKET=25, RBRACKET=26, + COMMA=27, EQUALS=28, FLAG=29, DASH_DASH=30, DOUBLE_QUOTED_STRING=31, SINGLE_QUOTED_STRING=32, + ENV_VAR=33, SPECIAL_VAR=34, COMMAND_SUBST=35, BACKTICK_SUBST=36, UNQUOTED_TEXT=37, + WS=38, NEWLINE=39, HP_LINE_CONTINUATION=40, HP_WS=41, HP_COMMENT=42, HP_LINE_COMMENT=43, + HEREDOC_CONTENT=44, H_NEWLINE=45; + public static final int + HEREDOC_PREAMBLE=1, HEREDOC=2; + public static String[] channelNames = { + "DEFAULT_TOKEN_CHANNEL", "HIDDEN" + }; + + public static String[] modeNames = { + "DEFAULT_MODE", "HEREDOC_PREAMBLE", "HEREDOC" + }; + + private static String[] makeRuleNames() { + return new String[] { + "PARSER_DIRECTIVE", "COMMENT", "FROM", "RUN", "CMD", "NONE", "LABEL", + "EXPOSE", "ENV", "ADD", "COPY", "ENTRYPOINT", "VOLUME", "USER", "WORKDIR", + "ARG", "ONBUILD", "STOPSIGNAL", "HEALTHCHECK", "SHELL", "MAINTAINER", + "AS", "HEREDOC_START", "LINE_CONTINUATION", "LBRACKET", "RBRACKET", "COMMA", + "EQUALS", "FLAG", "DASH_DASH", "UNQUOTED_CHAR", "ESCAPED_CHAR", "DOUBLE_QUOTED_STRING", + "SINGLE_QUOTED_STRING", "INLINE_CONTINUATION", "ESCAPE_SEQUENCE", "HEX_DIGIT", + "ENV_VAR", "SPECIAL_VAR", "COMMAND_SUBST", "COMMAND_SUBST_INNER", "BACKTICK_SUBST", + "UNQUOTED_TEXT", "WS", "WS_CHAR", "NEWLINE", "NEWLINE_CHAR", "HP_LINE_CONTINUATION", + "HP_NEWLINE", "HP_WS", "HP_COMMENT", "HP_LINE_COMMENT", "HP_HEREDOC_START", + "HP_UNQUOTED_TEXT", "H_NEWLINE", "HEREDOC_CONTENT" + }; + } + public static final String[] ruleNames = makeRuleNames(); + + private static String[] makeLiteralNames() { + return new String[] { + null, null, null, "'FROM'", "'RUN'", "'CMD'", "'NONE'", "'LABEL'", "'EXPOSE'", + "'ENV'", "'ADD'", "'COPY'", "'ENTRYPOINT'", "'VOLUME'", "'USER'", "'WORKDIR'", + "'ARG'", "'ONBUILD'", "'STOPSIGNAL'", "'HEALTHCHECK'", "'SHELL'", "'MAINTAINER'", + "'AS'", null, null, "'['", "']'", "','", "'='", null, "'--'", null, null, + null, null, null, null, null, null, null, null, null, null, null, null, + "'\\n'" + }; + } + private static final String[] _LITERAL_NAMES = makeLiteralNames(); + private static String[] makeSymbolicNames() { + return new String[] { + null, "PARSER_DIRECTIVE", "COMMENT", "FROM", "RUN", "CMD", "NONE", "LABEL", + "EXPOSE", "ENV", "ADD", "COPY", "ENTRYPOINT", "VOLUME", "USER", "WORKDIR", + "ARG", "ONBUILD", "STOPSIGNAL", "HEALTHCHECK", "SHELL", "MAINTAINER", + "AS", "HEREDOC_START", "LINE_CONTINUATION", "LBRACKET", "RBRACKET", "COMMA", + "EQUALS", "FLAG", "DASH_DASH", "DOUBLE_QUOTED_STRING", "SINGLE_QUOTED_STRING", + "ENV_VAR", "SPECIAL_VAR", "COMMAND_SUBST", "BACKTICK_SUBST", "UNQUOTED_TEXT", + "WS", "NEWLINE", "HP_LINE_CONTINUATION", "HP_WS", "HP_COMMENT", "HP_LINE_COMMENT", + "HEREDOC_CONTENT", "H_NEWLINE" + }; + } + private static final String[] _SYMBOLIC_NAMES = makeSymbolicNames(); + public static final Vocabulary VOCABULARY = new VocabularyImpl(_LITERAL_NAMES, _SYMBOLIC_NAMES); + + /** + * @deprecated Use {@link #VOCABULARY} instead. + */ + @Deprecated + public static final String[] tokenNames; + static { + tokenNames = new String[_SYMBOLIC_NAMES.length]; + for (int i = 0; i < tokenNames.length; i++) { + tokenNames[i] = VOCABULARY.getLiteralName(i); + if (tokenNames[i] == null) { + tokenNames[i] = VOCABULARY.getSymbolicName(i); + } + + if (tokenNames[i] == null) { + tokenNames[i] = ""; + } + } + } + + @Override + @Deprecated + public String[] getTokenNames() { + return tokenNames; + } + + @Override + + public Vocabulary getVocabulary() { + return VOCABULARY; + } + + + // Use a queue (FIFO) for heredoc markers so they are matched in order of declaration + private Queue heredocIdentifiers = new LinkedList(); + private boolean heredocIdentifierCaptured = false; + // Track if we're at the start of a logical line (where instructions can appear) + private boolean atLineStart = true; + // Track if we're after FROM to recognize AS as a keyword (for stage aliasing) + private boolean afterFrom = false; + // Track if we're after HEALTHCHECK to recognize CMD/NONE as keywords + private boolean afterHealthcheck = false; + + + public DockerLexer(CharStream input) { + super(input); + _interp = new LexerATNSimulator(this,_ATN,_decisionToDFA,_sharedContextCache); + } + + @Override + public String getGrammarFileName() { return "DockerLexer.g4"; } + + @Override + public String[] getRuleNames() { return ruleNames; } + + @Override + public String getSerializedATN() { return _serializedATN; } + + @Override + public String[] getChannelNames() { return channelNames; } + + @Override + public String[] getModeNames() { return modeNames; } + + @Override + public ATN getATN() { return _ATN; } + + @Override + public void action(RuleContext _localctx, int ruleIndex, int actionIndex) { + switch (ruleIndex) { + case 0: + PARSER_DIRECTIVE_action((RuleContext)_localctx, actionIndex); + break; + case 2: + FROM_action((RuleContext)_localctx, actionIndex); + break; + case 3: + RUN_action((RuleContext)_localctx, actionIndex); + break; + case 4: + CMD_action((RuleContext)_localctx, actionIndex); + break; + case 5: + NONE_action((RuleContext)_localctx, actionIndex); + break; + case 6: + LABEL_action((RuleContext)_localctx, actionIndex); + break; + case 7: + EXPOSE_action((RuleContext)_localctx, actionIndex); + break; + case 8: + ENV_action((RuleContext)_localctx, actionIndex); + break; + case 9: + ADD_action((RuleContext)_localctx, actionIndex); + break; + case 10: + COPY_action((RuleContext)_localctx, actionIndex); + break; + case 11: + ENTRYPOINT_action((RuleContext)_localctx, actionIndex); + break; + case 12: + VOLUME_action((RuleContext)_localctx, actionIndex); + break; + case 13: + USER_action((RuleContext)_localctx, actionIndex); + break; + case 14: + WORKDIR_action((RuleContext)_localctx, actionIndex); + break; + case 15: + ARG_action((RuleContext)_localctx, actionIndex); + break; + case 16: + ONBUILD_action((RuleContext)_localctx, actionIndex); + break; + case 17: + STOPSIGNAL_action((RuleContext)_localctx, actionIndex); + break; + case 18: + HEALTHCHECK_action((RuleContext)_localctx, actionIndex); + break; + case 19: + SHELL_action((RuleContext)_localctx, actionIndex); + break; + case 20: + MAINTAINER_action((RuleContext)_localctx, actionIndex); + break; + case 21: + AS_action((RuleContext)_localctx, actionIndex); + break; + case 22: + HEREDOC_START_action((RuleContext)_localctx, actionIndex); + break; + case 24: + LBRACKET_action((RuleContext)_localctx, actionIndex); + break; + case 25: + RBRACKET_action((RuleContext)_localctx, actionIndex); + break; + case 26: + COMMA_action((RuleContext)_localctx, actionIndex); + break; + case 27: + EQUALS_action((RuleContext)_localctx, actionIndex); + break; + case 28: + FLAG_action((RuleContext)_localctx, actionIndex); + break; + case 29: + DASH_DASH_action((RuleContext)_localctx, actionIndex); + break; + case 32: + DOUBLE_QUOTED_STRING_action((RuleContext)_localctx, actionIndex); + break; + case 33: + SINGLE_QUOTED_STRING_action((RuleContext)_localctx, actionIndex); + break; + case 37: + ENV_VAR_action((RuleContext)_localctx, actionIndex); + break; + case 38: + SPECIAL_VAR_action((RuleContext)_localctx, actionIndex); + break; + case 39: + COMMAND_SUBST_action((RuleContext)_localctx, actionIndex); + break; + case 41: + BACKTICK_SUBST_action((RuleContext)_localctx, actionIndex); + break; + case 42: + UNQUOTED_TEXT_action((RuleContext)_localctx, actionIndex); + break; + case 45: + NEWLINE_action((RuleContext)_localctx, actionIndex); + break; + case 52: + HP_HEREDOC_START_action((RuleContext)_localctx, actionIndex); + break; + case 55: + HEREDOC_CONTENT_action((RuleContext)_localctx, actionIndex); + break; + } + } + private void PARSER_DIRECTIVE_action(RuleContext _localctx, int actionIndex) { + switch (actionIndex) { + case 0: + atLineStart = true; + break; + } + } + private void FROM_action(RuleContext _localctx, int actionIndex) { + switch (actionIndex) { + case 1: + if (!atLineStart) setType(UNQUOTED_TEXT); else afterFrom = true; atLineStart = false; + break; + } + } + private void RUN_action(RuleContext _localctx, int actionIndex) { + switch (actionIndex) { + case 2: + if (!atLineStart) setType(UNQUOTED_TEXT); atLineStart = false; + break; + } + } + private void CMD_action(RuleContext _localctx, int actionIndex) { + switch (actionIndex) { + case 3: + if (!atLineStart && !afterHealthcheck) setType(UNQUOTED_TEXT); atLineStart = false; afterHealthcheck = false; + break; + } + } + private void NONE_action(RuleContext _localctx, int actionIndex) { + switch (actionIndex) { + case 4: + if (!afterHealthcheck) setType(UNQUOTED_TEXT); atLineStart = false; afterHealthcheck = false; + break; + } + } + private void LABEL_action(RuleContext _localctx, int actionIndex) { + switch (actionIndex) { + case 5: + if (!atLineStart) setType(UNQUOTED_TEXT); atLineStart = false; + break; + } + } + private void EXPOSE_action(RuleContext _localctx, int actionIndex) { + switch (actionIndex) { + case 6: + if (!atLineStart) setType(UNQUOTED_TEXT); atLineStart = false; + break; + } + } + private void ENV_action(RuleContext _localctx, int actionIndex) { + switch (actionIndex) { + case 7: + if (!atLineStart) setType(UNQUOTED_TEXT); atLineStart = false; + break; + } + } + private void ADD_action(RuleContext _localctx, int actionIndex) { + switch (actionIndex) { + case 8: + if (!atLineStart) setType(UNQUOTED_TEXT); atLineStart = false; + break; + } + } + private void COPY_action(RuleContext _localctx, int actionIndex) { + switch (actionIndex) { + case 9: + if (!atLineStart) setType(UNQUOTED_TEXT); atLineStart = false; + break; + } + } + private void ENTRYPOINT_action(RuleContext _localctx, int actionIndex) { + switch (actionIndex) { + case 10: + if (!atLineStart) setType(UNQUOTED_TEXT); atLineStart = false; + break; + } + } + private void VOLUME_action(RuleContext _localctx, int actionIndex) { + switch (actionIndex) { + case 11: + if (!atLineStart) setType(UNQUOTED_TEXT); atLineStart = false; + break; + } + } + private void USER_action(RuleContext _localctx, int actionIndex) { + switch (actionIndex) { + case 12: + if (!atLineStart) setType(UNQUOTED_TEXT); atLineStart = false; + break; + } + } + private void WORKDIR_action(RuleContext _localctx, int actionIndex) { + switch (actionIndex) { + case 13: + if (!atLineStart) setType(UNQUOTED_TEXT); atLineStart = false; + break; + } + } + private void ARG_action(RuleContext _localctx, int actionIndex) { + switch (actionIndex) { + case 14: + if (!atLineStart) setType(UNQUOTED_TEXT); atLineStart = false; + break; + } + } + private void ONBUILD_action(RuleContext _localctx, int actionIndex) { + switch (actionIndex) { + case 15: + if (!atLineStart) setType(UNQUOTED_TEXT); /* atLineStart stays true */ + break; + } + } + private void STOPSIGNAL_action(RuleContext _localctx, int actionIndex) { + switch (actionIndex) { + case 16: + if (!atLineStart) setType(UNQUOTED_TEXT); atLineStart = false; + break; + } + } + private void HEALTHCHECK_action(RuleContext _localctx, int actionIndex) { + switch (actionIndex) { + case 17: + if (!atLineStart) setType(UNQUOTED_TEXT); else afterHealthcheck = true; /* atLineStart stays true */ + break; + } + } + private void SHELL_action(RuleContext _localctx, int actionIndex) { + switch (actionIndex) { + case 18: + if (!atLineStart) setType(UNQUOTED_TEXT); atLineStart = false; + break; + } + } + private void MAINTAINER_action(RuleContext _localctx, int actionIndex) { + switch (actionIndex) { + case 19: + if (!atLineStart) setType(UNQUOTED_TEXT); atLineStart = false; + break; + } + } + private void AS_action(RuleContext _localctx, int actionIndex) { + switch (actionIndex) { + case 20: + if (!afterFrom) setType(UNQUOTED_TEXT); atLineStart = false; afterFrom = false; + break; + } + } + private void HEREDOC_START_action(RuleContext _localctx, int actionIndex) { + switch (actionIndex) { + case 21: + + // Extract and store the heredoc marker identifier in FIFO order + String text = getText(); + int prefixLen = text.charAt(2) == '-' ? 3 : 2; + String marker = text.substring(prefixLen); + heredocIdentifiers.add(marker); + heredocIdentifierCaptured = true; + atLineStart = false; + + break; + } + } + private void LBRACKET_action(RuleContext _localctx, int actionIndex) { + switch (actionIndex) { + case 22: + atLineStart = false; + break; + } + } + private void RBRACKET_action(RuleContext _localctx, int actionIndex) { + switch (actionIndex) { + case 23: + atLineStart = false; + break; + } + } + private void COMMA_action(RuleContext _localctx, int actionIndex) { + switch (actionIndex) { + case 24: + atLineStart = false; + break; + } + } + private void EQUALS_action(RuleContext _localctx, int actionIndex) { + switch (actionIndex) { + case 25: + if (!afterHealthcheck) atLineStart = false; + break; + } + } + private void FLAG_action(RuleContext _localctx, int actionIndex) { + switch (actionIndex) { + case 26: + if (!afterHealthcheck) atLineStart = false; + break; + } + } + private void DASH_DASH_action(RuleContext _localctx, int actionIndex) { + switch (actionIndex) { + case 27: + if (!afterHealthcheck) atLineStart = false; + break; + } + } + private void DOUBLE_QUOTED_STRING_action(RuleContext _localctx, int actionIndex) { + switch (actionIndex) { + case 28: + if (!afterHealthcheck) atLineStart = false; + break; + } + } + private void SINGLE_QUOTED_STRING_action(RuleContext _localctx, int actionIndex) { + switch (actionIndex) { + case 29: + if (!afterHealthcheck) atLineStart = false; + break; + } + } + private void ENV_VAR_action(RuleContext _localctx, int actionIndex) { + switch (actionIndex) { + case 30: + atLineStart = false; + break; + } + } + private void SPECIAL_VAR_action(RuleContext _localctx, int actionIndex) { + switch (actionIndex) { + case 31: + atLineStart = false; + break; + } + } + private void COMMAND_SUBST_action(RuleContext _localctx, int actionIndex) { + switch (actionIndex) { + case 32: + atLineStart = false; + break; + } + } + private void BACKTICK_SUBST_action(RuleContext _localctx, int actionIndex) { + switch (actionIndex) { + case 33: + atLineStart = false; + break; + } + } + private void UNQUOTED_TEXT_action(RuleContext _localctx, int actionIndex) { + switch (actionIndex) { + case 34: + if (!afterHealthcheck) atLineStart = false; + break; + } + } + private void NEWLINE_action(RuleContext _localctx, int actionIndex) { + switch (actionIndex) { + case 35: + atLineStart = true; afterFrom = false; afterHealthcheck = false; + break; + } + } + private void HP_HEREDOC_START_action(RuleContext _localctx, int actionIndex) { + switch (actionIndex) { + case 36: + + // Extract and store the heredoc marker identifier in FIFO order + String text = getText(); + int prefixLen = text.charAt(2) == '-' ? 3 : 2; + String marker = text.substring(prefixLen); + heredocIdentifiers.add(marker); + + break; + } + } + private void HEREDOC_CONTENT_action(RuleContext _localctx, int actionIndex) { + switch (actionIndex) { + case 37: + + if(!heredocIdentifiers.isEmpty() && getText().equals(heredocIdentifiers.peek())) { + setType(UNQUOTED_TEXT); + heredocIdentifiers.poll(); // Remove from front of queue (FIFO) + // Only pop mode when all heredoc markers have been matched + if(heredocIdentifiers.isEmpty()) { + popMode(); + atLineStart = true; // After heredoc ends, next line is at line start + } + } + + break; + } + } + + public static final String _serializedATN = + "\u0004\u0000-\u02a2\u0006\uffff\uffff\u0006\uffff\uffff\u0006\uffff\uffff"+ + "\u0002\u0000\u0007\u0000\u0002\u0001\u0007\u0001\u0002\u0002\u0007\u0002"+ + "\u0002\u0003\u0007\u0003\u0002\u0004\u0007\u0004\u0002\u0005\u0007\u0005"+ + "\u0002\u0006\u0007\u0006\u0002\u0007\u0007\u0007\u0002\b\u0007\b\u0002"+ + "\t\u0007\t\u0002\n\u0007\n\u0002\u000b\u0007\u000b\u0002\f\u0007\f\u0002"+ + "\r\u0007\r\u0002\u000e\u0007\u000e\u0002\u000f\u0007\u000f\u0002\u0010"+ + "\u0007\u0010\u0002\u0011\u0007\u0011\u0002\u0012\u0007\u0012\u0002\u0013"+ + "\u0007\u0013\u0002\u0014\u0007\u0014\u0002\u0015\u0007\u0015\u0002\u0016"+ + "\u0007\u0016\u0002\u0017\u0007\u0017\u0002\u0018\u0007\u0018\u0002\u0019"+ + "\u0007\u0019\u0002\u001a\u0007\u001a\u0002\u001b\u0007\u001b\u0002\u001c"+ + "\u0007\u001c\u0002\u001d\u0007\u001d\u0002\u001e\u0007\u001e\u0002\u001f"+ + "\u0007\u001f\u0002 \u0007 \u0002!\u0007!\u0002\"\u0007\"\u0002#\u0007"+ + "#\u0002$\u0007$\u0002%\u0007%\u0002&\u0007&\u0002\'\u0007\'\u0002(\u0007"+ + "(\u0002)\u0007)\u0002*\u0007*\u0002+\u0007+\u0002,\u0007,\u0002-\u0007"+ + "-\u0002.\u0007.\u0002/\u0007/\u00020\u00070\u00021\u00071\u00022\u0007"+ + "2\u00023\u00073\u00024\u00074\u00025\u00075\u00026\u00076\u00027\u0007"+ + "7\u0001\u0000\u0001\u0000\u0005\u0000v\b\u0000\n\u0000\f\u0000y\t\u0000"+ + "\u0001\u0000\u0004\u0000|\b\u0000\u000b\u0000\f\u0000}\u0001\u0000\u0005"+ + "\u0000\u0081\b\u0000\n\u0000\f\u0000\u0084\t\u0000\u0001\u0000\u0001\u0000"+ + "\u0005\u0000\u0088\b\u0000\n\u0000\f\u0000\u008b\t\u0000\u0001\u0000\u0005"+ + "\u0000\u008e\b\u0000\n\u0000\f\u0000\u0091\t\u0000\u0001\u0000\u0001\u0000"+ + "\u0001\u0000\u0001\u0001\u0001\u0001\u0005\u0001\u0098\b\u0001\n\u0001"+ + "\f\u0001\u009b\t\u0001\u0001\u0001\u0001\u0001\u0001\u0002\u0001\u0002"+ + "\u0001\u0002\u0001\u0002\u0001\u0002\u0001\u0002\u0001\u0002\u0001\u0003"+ + "\u0001\u0003\u0001\u0003\u0001\u0003\u0001\u0003\u0001\u0003\u0001\u0004"+ + "\u0001\u0004\u0001\u0004\u0001\u0004\u0001\u0004\u0001\u0004\u0001\u0005"+ + "\u0001\u0005\u0001\u0005\u0001\u0005\u0001\u0005\u0001\u0005\u0001\u0005"+ + "\u0001\u0006\u0001\u0006\u0001\u0006\u0001\u0006\u0001\u0006\u0001\u0006"+ + "\u0001\u0006\u0001\u0006\u0001\u0007\u0001\u0007\u0001\u0007\u0001\u0007"+ + "\u0001\u0007\u0001\u0007\u0001\u0007\u0001\u0007\u0001\u0007\u0001\b\u0001"+ + "\b\u0001\b\u0001\b\u0001\b\u0001\b\u0001\t\u0001\t\u0001\t\u0001\t\u0001"+ + "\t\u0001\t\u0001\n\u0001\n\u0001\n\u0001\n\u0001\n\u0001\n\u0001\n\u0001"+ + "\u000b\u0001\u000b\u0001\u000b\u0001\u000b\u0001\u000b\u0001\u000b\u0001"+ + "\u000b\u0001\u000b\u0001\u000b\u0001\u000b\u0001\u000b\u0001\u000b\u0001"+ + "\u000b\u0001\f\u0001\f\u0001\f\u0001\f\u0001\f\u0001\f\u0001\f\u0001\f"+ + "\u0001\f\u0001\r\u0001\r\u0001\r\u0001\r\u0001\r\u0001\r\u0001\r\u0001"+ + "\u000e\u0001\u000e\u0001\u000e\u0001\u000e\u0001\u000e\u0001\u000e\u0001"+ + "\u000e\u0001\u000e\u0001\u000e\u0001\u000e\u0001\u000f\u0001\u000f\u0001"+ + "\u000f\u0001\u000f\u0001\u000f\u0001\u000f\u0001\u0010\u0001\u0010\u0001"+ + "\u0010\u0001\u0010\u0001\u0010\u0001\u0010\u0001\u0010\u0001\u0010\u0001"+ + "\u0010\u0001\u0010\u0001\u0011\u0001\u0011\u0001\u0011\u0001\u0011\u0001"+ + "\u0011\u0001\u0011\u0001\u0011\u0001\u0011\u0001\u0011\u0001\u0011\u0001"+ + "\u0011\u0001\u0011\u0001\u0011\u0001\u0012\u0001\u0012\u0001\u0012\u0001"+ + "\u0012\u0001\u0012\u0001\u0012\u0001\u0012\u0001\u0012\u0001\u0012\u0001"+ + "\u0012\u0001\u0012\u0001\u0012\u0001\u0012\u0001\u0012\u0001\u0013\u0001"+ + "\u0013\u0001\u0013\u0001\u0013\u0001\u0013\u0001\u0013\u0001\u0013\u0001"+ + "\u0013\u0001\u0014\u0001\u0014\u0001\u0014\u0001\u0014\u0001\u0014\u0001"+ + "\u0014\u0001\u0014\u0001\u0014\u0001\u0014\u0001\u0014\u0001\u0014\u0001"+ + "\u0014\u0001\u0014\u0001\u0015\u0001\u0015\u0001\u0015\u0001\u0015\u0001"+ + "\u0015\u0001\u0016\u0001\u0016\u0001\u0016\u0001\u0016\u0003\u0016\u014d"+ + "\b\u0016\u0001\u0016\u0001\u0016\u0005\u0016\u0151\b\u0016\n\u0016\f\u0016"+ + "\u0154\t\u0016\u0001\u0016\u0001\u0016\u0001\u0016\u0001\u0016\u0001\u0017"+ + "\u0001\u0017\u0005\u0017\u015c\b\u0017\n\u0017\f\u0017\u015f\t\u0017\u0001"+ + "\u0017\u0001\u0017\u0001\u0017\u0001\u0017\u0001\u0018\u0001\u0018\u0001"+ + "\u0018\u0001\u0019\u0001\u0019\u0001\u0019\u0001\u001a\u0001\u001a\u0001"+ + "\u001a\u0001\u001b\u0001\u001b\u0001\u001b\u0001\u001c\u0001\u001c\u0001"+ + "\u001c\u0001\u001c\u0001\u001c\u0005\u001c\u0176\b\u001c\n\u001c\f\u001c"+ + "\u0179\t\u001c\u0001\u001c\u0001\u001c\u0004\u001c\u017d\b\u001c\u000b"+ + "\u001c\f\u001c\u017e\u0003\u001c\u0181\b\u001c\u0001\u001c\u0001\u001c"+ + "\u0001\u001d\u0001\u001d\u0001\u001d\u0001\u001d\u0001\u001d\u0001\u001e"+ + "\u0001\u001e\u0001\u001f\u0001\u001f\u0001\u001f\u0001 \u0001 \u0001 "+ + "\u0001 \u0001 \u0005 \u0194\b \n \f \u0197\t \u0001 \u0001 \u0001 \u0001"+ + "!\u0001!\u0001!\u0001!\u0005!\u01a0\b!\n!\f!\u01a3\t!\u0001!\u0001!\u0001"+ + "!\u0001\"\u0001\"\u0005\"\u01aa\b\"\n\"\f\"\u01ad\t\"\u0001\"\u0004\""+ + "\u01b0\b\"\u000b\"\f\"\u01b1\u0001#\u0001#\u0001#\u0001$\u0001$\u0001"+ + "%\u0001%\u0001%\u0001%\u0005%\u01bd\b%\n%\f%\u01c0\t%\u0001%\u0001%\u0001"+ + "%\u0001%\u0001%\u0003%\u01c7\b%\u0001%\u0005%\u01ca\b%\n%\f%\u01cd\t%"+ + "\u0001%\u0001%\u0001%\u0001%\u0005%\u01d3\b%\n%\f%\u01d6\t%\u0003%\u01d8"+ + "\b%\u0001%\u0001%\u0001&\u0001&\u0001&\u0001&\u0001\'\u0001\'\u0001\'"+ + "\u0001\'\u0001\'\u0001\'\u0001\'\u0005\'\u01e7\b\'\n\'\f\'\u01ea\t\'\u0001"+ + "\'\u0005\'\u01ed\b\'\n\'\f\'\u01f0\t\'\u0001\'\u0001\'\u0001\'\u0001("+ + "\u0001(\u0003(\u01f7\b(\u0001)\u0001)\u0001)\u0005)\u01fc\b)\n)\f)\u01ff"+ + "\t)\u0001)\u0001)\u0001)\u0001*\u0001*\u0001*\u0005*\u0207\b*\n*\f*\u020a"+ + "\t*\u0001*\u0001*\u0001*\u0001*\u0005*\u0210\b*\n*\f*\u0213\t*\u0001*"+ + "\u0001*\u0001*\u0001*\u0001*\u0005*\u021a\b*\n*\f*\u021d\t*\u0001*\u0001"+ + "*\u0001*\u0001*\u0005*\u0223\b*\n*\f*\u0226\t*\u0003*\u0228\b*\u0001*"+ + "\u0001*\u0001+\u0004+\u022d\b+\u000b+\f+\u022e\u0001+\u0001+\u0001,\u0001"+ + ",\u0001-\u0004-\u0236\b-\u000b-\f-\u0237\u0001-\u0001-\u0001-\u0001-\u0001"+ + ".\u0001.\u0001/\u0001/\u0005/\u0242\b/\n/\f/\u0245\t/\u0001/\u0001/\u0001"+ + "/\u0001/\u00010\u00010\u00010\u00010\u00010\u00011\u00041\u0251\b1\u000b"+ + "1\f1\u0252\u00011\u00011\u00012\u00012\u00012\u00012\u00052\u025b\b2\n"+ + "2\f2\u025e\t2\u00012\u00012\u00012\u00012\u00012\u00013\u00013\u00013"+ + "\u00033\u0268\b3\u00013\u00053\u026b\b3\n3\f3\u026e\t3\u00013\u00033\u0271"+ + "\b3\u00013\u00013\u00014\u00014\u00014\u00014\u00034\u0279\b4\u00014\u0001"+ + "4\u00054\u027d\b4\n4\f4\u0280\t4\u00014\u00014\u00014\u00014\u00015\u0004"+ + "5\u0287\b5\u000b5\f5\u0288\u00015\u00015\u00015\u00055\u028e\b5\n5\f5"+ + "\u0291\t5\u00015\u00035\u0294\b5\u00015\u00015\u00016\u00016\u00016\u0001"+ + "6\u00017\u00047\u029d\b7\u000b7\f7\u029e\u00017\u00017\u0001\u025c\u0000"+ + "8\u0003\u0001\u0005\u0002\u0007\u0003\t\u0004\u000b\u0005\r\u0006\u000f"+ + "\u0007\u0011\b\u0013\t\u0015\n\u0017\u000b\u0019\f\u001b\r\u001d\u000e"+ + "\u001f\u000f!\u0010#\u0011%\u0012\'\u0013)\u0014+\u0015-\u0016/\u0017"+ + "1\u00183\u00195\u001a7\u001b9\u001c;\u001d=\u001e?\u0000A\u0000C\u001f"+ + "E G\u0000I\u0000K\u0000M!O\"Q#S\u0000U$W%Y&[\u0000]\'_\u0000a(c\u0000"+ + "e)g*i+k\u0000m\u0000o-q,\u0003\u0000\u0001\u0002-\u0003\u0000AZ__az\u0002"+ + "\u0000\n\n\r\r\u0002\u0000FFff\u0002\u0000RRrr\u0002\u0000OOoo\u0002\u0000"+ + "MMmm\u0002\u0000UUuu\u0002\u0000NNnn\u0002\u0000CCcc\u0002\u0000DDdd\u0002"+ + "\u0000EEee\u0002\u0000LLll\u0002\u0000AAaa\u0002\u0000BBbb\u0002\u0000"+ + "XXxx\u0002\u0000PPpp\u0002\u0000SSss\u0002\u0000VVvv\u0002\u0000YYyy\u0002"+ + "\u0000TTtt\u0002\u0000IIii\u0002\u0000WWww\u0002\u0000KKkk\u0002\u0000"+ + "GGgg\u0002\u0000HHhh\u0004\u000009AZ__az\u0002\u0000\\\\``\u0002\u0000"+ + "\t\t \u0002\u0000AZaz\u0005\u0000--09AZ__az\u0003\u0000\t\n\r\r \b\u0000"+ + "\t\n\r\r \"\"$$\'\'<=[]\u0003\u0000\n\n\r\r``\u0005\u0000\n\n\r\r\"\""+ + "\\\\``\u0003\u0000\n\n\r\r\'\'\u0003\u000009AFaf\u0001\u0000}}\u0005\u0000"+ + "!!#$**09?@\u0001\u0000()\u0004\u0000\t\n\r\r ``\t\u0000\t\n\r\r \"\""+ + "$$\'\'--<=[]\u0003\u0000\t\t\f\r \u0006\u0000\t\n\r\r <<\\\\``\u0004"+ + "\u0000\t\n\r\r <<\u0001\u0000\n\n\u02d5\u0000\u0003\u0001\u0000\u0000"+ + "\u0000\u0000\u0005\u0001\u0000\u0000\u0000\u0000\u0007\u0001\u0000\u0000"+ + "\u0000\u0000\t\u0001\u0000\u0000\u0000\u0000\u000b\u0001\u0000\u0000\u0000"+ + "\u0000\r\u0001\u0000\u0000\u0000\u0000\u000f\u0001\u0000\u0000\u0000\u0000"+ + "\u0011\u0001\u0000\u0000\u0000\u0000\u0013\u0001\u0000\u0000\u0000\u0000"+ + "\u0015\u0001\u0000\u0000\u0000\u0000\u0017\u0001\u0000\u0000\u0000\u0000"+ + "\u0019\u0001\u0000\u0000\u0000\u0000\u001b\u0001\u0000\u0000\u0000\u0000"+ + "\u001d\u0001\u0000\u0000\u0000\u0000\u001f\u0001\u0000\u0000\u0000\u0000"+ + "!\u0001\u0000\u0000\u0000\u0000#\u0001\u0000\u0000\u0000\u0000%\u0001"+ + "\u0000\u0000\u0000\u0000\'\u0001\u0000\u0000\u0000\u0000)\u0001\u0000"+ + "\u0000\u0000\u0000+\u0001\u0000\u0000\u0000\u0000-\u0001\u0000\u0000\u0000"+ + "\u0000/\u0001\u0000\u0000\u0000\u00001\u0001\u0000\u0000\u0000\u00003"+ + "\u0001\u0000\u0000\u0000\u00005\u0001\u0000\u0000\u0000\u00007\u0001\u0000"+ + "\u0000\u0000\u00009\u0001\u0000\u0000\u0000\u0000;\u0001\u0000\u0000\u0000"+ + "\u0000=\u0001\u0000\u0000\u0000\u0000C\u0001\u0000\u0000\u0000\u0000E"+ + "\u0001\u0000\u0000\u0000\u0000M\u0001\u0000\u0000\u0000\u0000O\u0001\u0000"+ + "\u0000\u0000\u0000Q\u0001\u0000\u0000\u0000\u0000U\u0001\u0000\u0000\u0000"+ + "\u0000W\u0001\u0000\u0000\u0000\u0000Y\u0001\u0000\u0000\u0000\u0000]"+ + "\u0001\u0000\u0000\u0000\u0001a\u0001\u0000\u0000\u0000\u0001c\u0001\u0000"+ + "\u0000\u0000\u0001e\u0001\u0000\u0000\u0000\u0001g\u0001\u0000\u0000\u0000"+ + "\u0001i\u0001\u0000\u0000\u0000\u0001k\u0001\u0000\u0000\u0000\u0001m"+ + "\u0001\u0000\u0000\u0000\u0002o\u0001\u0000\u0000\u0000\u0002q\u0001\u0000"+ + "\u0000\u0000\u0003s\u0001\u0000\u0000\u0000\u0005\u0095\u0001\u0000\u0000"+ + "\u0000\u0007\u009e\u0001\u0000\u0000\u0000\t\u00a5\u0001\u0000\u0000\u0000"+ + "\u000b\u00ab\u0001\u0000\u0000\u0000\r\u00b1\u0001\u0000\u0000\u0000\u000f"+ + "\u00b8\u0001\u0000\u0000\u0000\u0011\u00c0\u0001\u0000\u0000\u0000\u0013"+ + "\u00c9\u0001\u0000\u0000\u0000\u0015\u00cf\u0001\u0000\u0000\u0000\u0017"+ + "\u00d5\u0001\u0000\u0000\u0000\u0019\u00dc\u0001\u0000\u0000\u0000\u001b"+ + "\u00e9\u0001\u0000\u0000\u0000\u001d\u00f2\u0001\u0000\u0000\u0000\u001f"+ + "\u00f9\u0001\u0000\u0000\u0000!\u0103\u0001\u0000\u0000\u0000#\u0109\u0001"+ + "\u0000\u0000\u0000%\u0113\u0001\u0000\u0000\u0000\'\u0120\u0001\u0000"+ + "\u0000\u0000)\u012e\u0001\u0000\u0000\u0000+\u0136\u0001\u0000\u0000\u0000"+ + "-\u0143\u0001\u0000\u0000\u0000/\u0148\u0001\u0000\u0000\u00001\u0159"+ + "\u0001\u0000\u0000\u00003\u0164\u0001\u0000\u0000\u00005\u0167\u0001\u0000"+ + "\u0000\u00007\u016a\u0001\u0000\u0000\u00009\u016d\u0001\u0000\u0000\u0000"+ + ";\u0170\u0001\u0000\u0000\u0000=\u0184\u0001\u0000\u0000\u0000?\u0189"+ + "\u0001\u0000\u0000\u0000A\u018b\u0001\u0000\u0000\u0000C\u018e\u0001\u0000"+ + "\u0000\u0000E\u019b\u0001\u0000\u0000\u0000G\u01a7\u0001\u0000\u0000\u0000"+ + "I\u01b3\u0001\u0000\u0000\u0000K\u01b6\u0001\u0000\u0000\u0000M\u01d7"+ + "\u0001\u0000\u0000\u0000O\u01db\u0001\u0000\u0000\u0000Q\u01df\u0001\u0000"+ + "\u0000\u0000S\u01f6\u0001\u0000\u0000\u0000U\u01f8\u0001\u0000\u0000\u0000"+ + "W\u0227\u0001\u0000\u0000\u0000Y\u022c\u0001\u0000\u0000\u0000[\u0232"+ + "\u0001\u0000\u0000\u0000]\u0235\u0001\u0000\u0000\u0000_\u023d\u0001\u0000"+ + "\u0000\u0000a\u023f\u0001\u0000\u0000\u0000c\u024a\u0001\u0000\u0000\u0000"+ + "e\u0250\u0001\u0000\u0000\u0000g\u0256\u0001\u0000\u0000\u0000i\u0267"+ + "\u0001\u0000\u0000\u0000k\u0274\u0001\u0000\u0000\u0000m\u0293\u0001\u0000"+ + "\u0000\u0000o\u0297\u0001\u0000\u0000\u0000q\u029c\u0001\u0000\u0000\u0000"+ + "sw\u0005#\u0000\u0000tv\u0003[,\u0000ut\u0001\u0000\u0000\u0000vy\u0001"+ + "\u0000\u0000\u0000wu\u0001\u0000\u0000\u0000wx\u0001\u0000\u0000\u0000"+ + "x{\u0001\u0000\u0000\u0000yw\u0001\u0000\u0000\u0000z|\u0007\u0000\u0000"+ + "\u0000{z\u0001\u0000\u0000\u0000|}\u0001\u0000\u0000\u0000}{\u0001\u0000"+ + "\u0000\u0000}~\u0001\u0000\u0000\u0000~\u0082\u0001\u0000\u0000\u0000"+ + "\u007f\u0081\u0003[,\u0000\u0080\u007f\u0001\u0000\u0000\u0000\u0081\u0084"+ + "\u0001\u0000\u0000\u0000\u0082\u0080\u0001\u0000\u0000\u0000\u0082\u0083"+ + "\u0001\u0000\u0000\u0000\u0083\u0085\u0001\u0000\u0000\u0000\u0084\u0082"+ + "\u0001\u0000\u0000\u0000\u0085\u0089\u0005=\u0000\u0000\u0086\u0088\u0003"+ + "[,\u0000\u0087\u0086\u0001\u0000\u0000\u0000\u0088\u008b\u0001\u0000\u0000"+ + "\u0000\u0089\u0087\u0001\u0000\u0000\u0000\u0089\u008a\u0001\u0000\u0000"+ + "\u0000\u008a\u008f\u0001\u0000\u0000\u0000\u008b\u0089\u0001\u0000\u0000"+ + "\u0000\u008c\u008e\b\u0001\u0000\u0000\u008d\u008c\u0001\u0000\u0000\u0000"+ + "\u008e\u0091\u0001\u0000\u0000\u0000\u008f\u008d\u0001\u0000\u0000\u0000"+ + "\u008f\u0090\u0001\u0000\u0000\u0000\u0090\u0092\u0001\u0000\u0000\u0000"+ + "\u0091\u008f\u0001\u0000\u0000\u0000\u0092\u0093\u0003_.\u0000\u0093\u0094"+ + "\u0006\u0000\u0000\u0000\u0094\u0004\u0001\u0000\u0000\u0000\u0095\u0099"+ + "\u0005#\u0000\u0000\u0096\u0098\b\u0001\u0000\u0000\u0097\u0096\u0001"+ + "\u0000\u0000\u0000\u0098\u009b\u0001\u0000\u0000\u0000\u0099\u0097\u0001"+ + "\u0000\u0000\u0000\u0099\u009a\u0001\u0000\u0000\u0000\u009a\u009c\u0001"+ + "\u0000\u0000\u0000\u009b\u0099\u0001\u0000\u0000\u0000\u009c\u009d\u0006"+ + "\u0001\u0001\u0000\u009d\u0006\u0001\u0000\u0000\u0000\u009e\u009f\u0007"+ + "\u0002\u0000\u0000\u009f\u00a0\u0007\u0003\u0000\u0000\u00a0\u00a1\u0007"+ + "\u0004\u0000\u0000\u00a1\u00a2\u0007\u0005\u0000\u0000\u00a2\u00a3\u0001"+ + "\u0000\u0000\u0000\u00a3\u00a4\u0006\u0002\u0002\u0000\u00a4\b\u0001\u0000"+ + "\u0000\u0000\u00a5\u00a6\u0007\u0003\u0000\u0000\u00a6\u00a7\u0007\u0006"+ + "\u0000\u0000\u00a7\u00a8\u0007\u0007\u0000\u0000\u00a8\u00a9\u0001\u0000"+ + "\u0000\u0000\u00a9\u00aa\u0006\u0003\u0003\u0000\u00aa\n\u0001\u0000\u0000"+ + "\u0000\u00ab\u00ac\u0007\b\u0000\u0000\u00ac\u00ad\u0007\u0005\u0000\u0000"+ + "\u00ad\u00ae\u0007\t\u0000\u0000\u00ae\u00af\u0001\u0000\u0000\u0000\u00af"+ + "\u00b0\u0006\u0004\u0004\u0000\u00b0\f\u0001\u0000\u0000\u0000\u00b1\u00b2"+ + "\u0007\u0007\u0000\u0000\u00b2\u00b3\u0007\u0004\u0000\u0000\u00b3\u00b4"+ + "\u0007\u0007\u0000\u0000\u00b4\u00b5\u0007\n\u0000\u0000\u00b5\u00b6\u0001"+ + "\u0000\u0000\u0000\u00b6\u00b7\u0006\u0005\u0005\u0000\u00b7\u000e\u0001"+ + "\u0000\u0000\u0000\u00b8\u00b9\u0007\u000b\u0000\u0000\u00b9\u00ba\u0007"+ + "\f\u0000\u0000\u00ba\u00bb\u0007\r\u0000\u0000\u00bb\u00bc\u0007\n\u0000"+ + "\u0000\u00bc\u00bd\u0007\u000b\u0000\u0000\u00bd\u00be\u0001\u0000\u0000"+ + "\u0000\u00be\u00bf\u0006\u0006\u0006\u0000\u00bf\u0010\u0001\u0000\u0000"+ + "\u0000\u00c0\u00c1\u0007\n\u0000\u0000\u00c1\u00c2\u0007\u000e\u0000\u0000"+ + "\u00c2\u00c3\u0007\u000f\u0000\u0000\u00c3\u00c4\u0007\u0004\u0000\u0000"+ + "\u00c4\u00c5\u0007\u0010\u0000\u0000\u00c5\u00c6\u0007\n\u0000\u0000\u00c6"+ + "\u00c7\u0001\u0000\u0000\u0000\u00c7\u00c8\u0006\u0007\u0007\u0000\u00c8"+ + "\u0012\u0001\u0000\u0000\u0000\u00c9\u00ca\u0007\n\u0000\u0000\u00ca\u00cb"+ + "\u0007\u0007\u0000\u0000\u00cb\u00cc\u0007\u0011\u0000\u0000\u00cc\u00cd"+ + "\u0001\u0000\u0000\u0000\u00cd\u00ce\u0006\b\b\u0000\u00ce\u0014\u0001"+ + "\u0000\u0000\u0000\u00cf\u00d0\u0007\f\u0000\u0000\u00d0\u00d1\u0007\t"+ + "\u0000\u0000\u00d1\u00d2\u0007\t\u0000\u0000\u00d2\u00d3\u0001\u0000\u0000"+ + "\u0000\u00d3\u00d4\u0006\t\t\u0000\u00d4\u0016\u0001\u0000\u0000\u0000"+ + "\u00d5\u00d6\u0007\b\u0000\u0000\u00d6\u00d7\u0007\u0004\u0000\u0000\u00d7"+ + "\u00d8\u0007\u000f\u0000\u0000\u00d8\u00d9\u0007\u0012\u0000\u0000\u00d9"+ + "\u00da\u0001\u0000\u0000\u0000\u00da\u00db\u0006\n\n\u0000\u00db\u0018"+ + "\u0001\u0000\u0000\u0000\u00dc\u00dd\u0007\n\u0000\u0000\u00dd\u00de\u0007"+ + "\u0007\u0000\u0000\u00de\u00df\u0007\u0013\u0000\u0000\u00df\u00e0\u0007"+ + "\u0003\u0000\u0000\u00e0\u00e1\u0007\u0012\u0000\u0000\u00e1\u00e2\u0007"+ + "\u000f\u0000\u0000\u00e2\u00e3\u0007\u0004\u0000\u0000\u00e3\u00e4\u0007"+ + "\u0014\u0000\u0000\u00e4\u00e5\u0007\u0007\u0000\u0000\u00e5\u00e6\u0007"+ + "\u0013\u0000\u0000\u00e6\u00e7\u0001\u0000\u0000\u0000\u00e7\u00e8\u0006"+ + "\u000b\u000b\u0000\u00e8\u001a\u0001\u0000\u0000\u0000\u00e9\u00ea\u0007"+ + "\u0011\u0000\u0000\u00ea\u00eb\u0007\u0004\u0000\u0000\u00eb\u00ec\u0007"+ + "\u000b\u0000\u0000\u00ec\u00ed\u0007\u0006\u0000\u0000\u00ed\u00ee\u0007"+ + "\u0005\u0000\u0000\u00ee\u00ef\u0007\n\u0000\u0000\u00ef\u00f0\u0001\u0000"+ + "\u0000\u0000\u00f0\u00f1\u0006\f\f\u0000\u00f1\u001c\u0001\u0000\u0000"+ + "\u0000\u00f2\u00f3\u0007\u0006\u0000\u0000\u00f3\u00f4\u0007\u0010\u0000"+ + "\u0000\u00f4\u00f5\u0007\n\u0000\u0000\u00f5\u00f6\u0007\u0003\u0000\u0000"+ + "\u00f6\u00f7\u0001\u0000\u0000\u0000\u00f7\u00f8\u0006\r\r\u0000\u00f8"+ + "\u001e\u0001\u0000\u0000\u0000\u00f9\u00fa\u0007\u0015\u0000\u0000\u00fa"+ + "\u00fb\u0007\u0004\u0000\u0000\u00fb\u00fc\u0007\u0003\u0000\u0000\u00fc"+ + "\u00fd\u0007\u0016\u0000\u0000\u00fd\u00fe\u0007\t\u0000\u0000\u00fe\u00ff"+ + "\u0007\u0014\u0000\u0000\u00ff\u0100\u0007\u0003\u0000\u0000\u0100\u0101"+ + "\u0001\u0000\u0000\u0000\u0101\u0102\u0006\u000e\u000e\u0000\u0102 \u0001"+ + "\u0000\u0000\u0000\u0103\u0104\u0007\f\u0000\u0000\u0104\u0105\u0007\u0003"+ + "\u0000\u0000\u0105\u0106\u0007\u0017\u0000\u0000\u0106\u0107\u0001\u0000"+ + "\u0000\u0000\u0107\u0108\u0006\u000f\u000f\u0000\u0108\"\u0001\u0000\u0000"+ + "\u0000\u0109\u010a\u0007\u0004\u0000\u0000\u010a\u010b\u0007\u0007\u0000"+ + "\u0000\u010b\u010c\u0007\r\u0000\u0000\u010c\u010d\u0007\u0006\u0000\u0000"+ + "\u010d\u010e\u0007\u0014\u0000\u0000\u010e\u010f\u0007\u000b\u0000\u0000"+ + "\u010f\u0110\u0007\t\u0000\u0000\u0110\u0111\u0001\u0000\u0000\u0000\u0111"+ + "\u0112\u0006\u0010\u0010\u0000\u0112$\u0001\u0000\u0000\u0000\u0113\u0114"+ + "\u0007\u0010\u0000\u0000\u0114\u0115\u0007\u0013\u0000\u0000\u0115\u0116"+ + "\u0007\u0004\u0000\u0000\u0116\u0117\u0007\u000f\u0000\u0000\u0117\u0118"+ + "\u0007\u0010\u0000\u0000\u0118\u0119\u0007\u0014\u0000\u0000\u0119\u011a"+ + "\u0007\u0017\u0000\u0000\u011a\u011b\u0007\u0007\u0000\u0000\u011b\u011c"+ + "\u0007\f\u0000\u0000\u011c\u011d\u0007\u000b\u0000\u0000\u011d\u011e\u0001"+ + "\u0000\u0000\u0000\u011e\u011f\u0006\u0011\u0011\u0000\u011f&\u0001\u0000"+ + "\u0000\u0000\u0120\u0121\u0007\u0018\u0000\u0000\u0121\u0122\u0007\n\u0000"+ + "\u0000\u0122\u0123\u0007\f\u0000\u0000\u0123\u0124\u0007\u000b\u0000\u0000"+ + "\u0124\u0125\u0007\u0013\u0000\u0000\u0125\u0126\u0007\u0018\u0000\u0000"+ + "\u0126\u0127\u0007\b\u0000\u0000\u0127\u0128\u0007\u0018\u0000\u0000\u0128"+ + "\u0129\u0007\n\u0000\u0000\u0129\u012a\u0007\b\u0000\u0000\u012a\u012b"+ + "\u0007\u0016\u0000\u0000\u012b\u012c\u0001\u0000\u0000\u0000\u012c\u012d"+ + "\u0006\u0012\u0012\u0000\u012d(\u0001\u0000\u0000\u0000\u012e\u012f\u0007"+ + "\u0010\u0000\u0000\u012f\u0130\u0007\u0018\u0000\u0000\u0130\u0131\u0007"+ + "\n\u0000\u0000\u0131\u0132\u0007\u000b\u0000\u0000\u0132\u0133\u0007\u000b"+ + "\u0000\u0000\u0133\u0134\u0001\u0000\u0000\u0000\u0134\u0135\u0006\u0013"+ + "\u0013\u0000\u0135*\u0001\u0000\u0000\u0000\u0136\u0137\u0007\u0005\u0000"+ + "\u0000\u0137\u0138\u0007\f\u0000\u0000\u0138\u0139\u0007\u0014\u0000\u0000"+ + "\u0139\u013a\u0007\u0007\u0000\u0000\u013a\u013b\u0007\u0013\u0000\u0000"+ + "\u013b\u013c\u0007\f\u0000\u0000\u013c\u013d\u0007\u0014\u0000\u0000\u013d"+ + "\u013e\u0007\u0007\u0000\u0000\u013e\u013f\u0007\n\u0000\u0000\u013f\u0140"+ + "\u0007\u0003\u0000\u0000\u0140\u0141\u0001\u0000\u0000\u0000\u0141\u0142"+ + "\u0006\u0014\u0014\u0000\u0142,\u0001\u0000\u0000\u0000\u0143\u0144\u0007"+ + "\f\u0000\u0000\u0144\u0145\u0007\u0010\u0000\u0000\u0145\u0146\u0001\u0000"+ + "\u0000\u0000\u0146\u0147\u0006\u0015\u0015\u0000\u0147.\u0001\u0000\u0000"+ + "\u0000\u0148\u0149\u0005<\u0000\u0000\u0149\u014a\u0005<\u0000\u0000\u014a"+ + "\u014c\u0001\u0000\u0000\u0000\u014b\u014d\u0005-\u0000\u0000\u014c\u014b"+ + "\u0001\u0000\u0000\u0000\u014c\u014d\u0001\u0000\u0000\u0000\u014d\u014e"+ + "\u0001\u0000\u0000\u0000\u014e\u0152\u0007\u0000\u0000\u0000\u014f\u0151"+ + "\u0007\u0019\u0000\u0000\u0150\u014f\u0001\u0000\u0000\u0000\u0151\u0154"+ + "\u0001\u0000\u0000\u0000\u0152\u0150\u0001\u0000\u0000\u0000\u0152\u0153"+ + "\u0001\u0000\u0000\u0000\u0153\u0155\u0001\u0000\u0000\u0000\u0154\u0152"+ + "\u0001\u0000\u0000\u0000\u0155\u0156\u0006\u0016\u0016\u0000\u0156\u0157"+ + "\u0001\u0000\u0000\u0000\u0157\u0158\u0006\u0016\u0017\u0000\u01580\u0001"+ + "\u0000\u0000\u0000\u0159\u015d\u0007\u001a\u0000\u0000\u015a\u015c\u0007"+ + "\u001b\u0000\u0000\u015b\u015a\u0001\u0000\u0000\u0000\u015c\u015f\u0001"+ + "\u0000\u0000\u0000\u015d\u015b\u0001\u0000\u0000\u0000\u015d\u015e\u0001"+ + "\u0000\u0000\u0000\u015e\u0160\u0001\u0000\u0000\u0000\u015f\u015d\u0001"+ + "\u0000\u0000\u0000\u0160\u0161\u0003_.\u0000\u0161\u0162\u0001\u0000\u0000"+ + "\u0000\u0162\u0163\u0006\u0017\u0001\u0000\u01632\u0001\u0000\u0000\u0000"+ + "\u0164\u0165\u0005[\u0000\u0000\u0165\u0166\u0006\u0018\u0018\u0000\u0166"+ + "4\u0001\u0000\u0000\u0000\u0167\u0168\u0005]\u0000\u0000\u0168\u0169\u0006"+ + "\u0019\u0019\u0000\u01696\u0001\u0000\u0000\u0000\u016a\u016b\u0005,\u0000"+ + "\u0000\u016b\u016c\u0006\u001a\u001a\u0000\u016c8\u0001\u0000\u0000\u0000"+ + "\u016d\u016e\u0005=\u0000\u0000\u016e\u016f\u0006\u001b\u001b\u0000\u016f"+ + ":\u0001\u0000\u0000\u0000\u0170\u0171\u0005-\u0000\u0000\u0171\u0172\u0005"+ + "-\u0000\u0000\u0172\u0173\u0001\u0000\u0000\u0000\u0173\u0177\u0007\u001c"+ + "\u0000\u0000\u0174\u0176\u0007\u001d\u0000\u0000\u0175\u0174\u0001\u0000"+ + "\u0000\u0000\u0176\u0179\u0001\u0000\u0000\u0000\u0177\u0175\u0001\u0000"+ + "\u0000\u0000\u0177\u0178\u0001\u0000\u0000\u0000\u0178\u0180\u0001\u0000"+ + "\u0000\u0000\u0179\u0177\u0001\u0000\u0000\u0000\u017a\u017c\u0005=\u0000"+ + "\u0000\u017b\u017d\b\u001e\u0000\u0000\u017c\u017b\u0001\u0000\u0000\u0000"+ + "\u017d\u017e\u0001\u0000\u0000\u0000\u017e\u017c\u0001\u0000\u0000\u0000"+ + "\u017e\u017f\u0001\u0000\u0000\u0000\u017f\u0181\u0001\u0000\u0000\u0000"+ + "\u0180\u017a\u0001\u0000\u0000\u0000\u0180\u0181\u0001\u0000\u0000\u0000"+ + "\u0181\u0182\u0001\u0000\u0000\u0000\u0182\u0183\u0006\u001c\u001c\u0000"+ + "\u0183<\u0001\u0000\u0000\u0000\u0184\u0185\u0005-\u0000\u0000\u0185\u0186"+ + "\u0005-\u0000\u0000\u0186\u0187\u0001\u0000\u0000\u0000\u0187\u0188\u0006"+ + "\u001d\u001d\u0000\u0188>\u0001\u0000\u0000\u0000\u0189\u018a\b\u001f"+ + "\u0000\u0000\u018a@\u0001\u0000\u0000\u0000\u018b\u018c\u0005\\\u0000"+ + "\u0000\u018c\u018d\t\u0000\u0000\u0000\u018dB\u0001\u0000\u0000\u0000"+ + "\u018e\u0195\u0005\"\u0000\u0000\u018f\u0194\u0003I#\u0000\u0190\u0194"+ + "\u0003G\"\u0000\u0191\u0194\u0007 \u0000\u0000\u0192\u0194\b!\u0000\u0000"+ + "\u0193\u018f\u0001\u0000\u0000\u0000\u0193\u0190\u0001\u0000\u0000\u0000"+ + "\u0193\u0191\u0001\u0000\u0000\u0000\u0193\u0192\u0001\u0000\u0000\u0000"+ + "\u0194\u0197\u0001\u0000\u0000\u0000\u0195\u0193\u0001\u0000\u0000\u0000"+ + "\u0195\u0196\u0001\u0000\u0000\u0000\u0196\u0198\u0001\u0000\u0000\u0000"+ + "\u0197\u0195\u0001\u0000\u0000\u0000\u0198\u0199\u0005\"\u0000\u0000\u0199"+ + "\u019a\u0006 \u001e\u0000\u019aD\u0001\u0000\u0000\u0000\u019b\u01a1\u0005"+ + "\'\u0000\u0000\u019c\u01a0\u0003G\"\u0000\u019d\u01a0\u0007\u0001\u0000"+ + "\u0000\u019e\u01a0\b\"\u0000\u0000\u019f\u019c\u0001\u0000\u0000\u0000"+ + "\u019f\u019d\u0001\u0000\u0000\u0000\u019f\u019e\u0001\u0000\u0000\u0000"+ + "\u01a0\u01a3\u0001\u0000\u0000\u0000\u01a1\u019f\u0001\u0000\u0000\u0000"+ + "\u01a1\u01a2\u0001\u0000\u0000\u0000\u01a2\u01a4\u0001\u0000\u0000\u0000"+ + "\u01a3\u01a1\u0001\u0000\u0000\u0000\u01a4\u01a5\u0005\'\u0000\u0000\u01a5"+ + "\u01a6\u0006!\u001f\u0000\u01a6F\u0001\u0000\u0000\u0000\u01a7\u01ab\u0007"+ + "\u001a\u0000\u0000\u01a8\u01aa\u0007\u001b\u0000\u0000\u01a9\u01a8\u0001"+ + "\u0000\u0000\u0000\u01aa\u01ad\u0001\u0000\u0000\u0000\u01ab\u01a9\u0001"+ + "\u0000\u0000\u0000\u01ab\u01ac\u0001\u0000\u0000\u0000\u01ac\u01af\u0001"+ + "\u0000\u0000\u0000\u01ad\u01ab\u0001\u0000\u0000\u0000\u01ae\u01b0\u0007"+ + "\u0001\u0000\u0000\u01af\u01ae\u0001\u0000\u0000\u0000\u01b0\u01b1\u0001"+ + "\u0000\u0000\u0000\u01b1\u01af\u0001\u0000\u0000\u0000\u01b1\u01b2\u0001"+ + "\u0000\u0000\u0000\u01b2H\u0001\u0000\u0000\u0000\u01b3\u01b4\u0005\\"+ + "\u0000\u0000\u01b4\u01b5\b\u0001\u0000\u0000\u01b5J\u0001\u0000\u0000"+ + "\u0000\u01b6\u01b7\u0007#\u0000\u0000\u01b7L\u0001\u0000\u0000\u0000\u01b8"+ + "\u01b9\u0005$\u0000\u0000\u01b9\u01ba\u0005{\u0000\u0000\u01ba\u01be\u0007"+ + "\u0000\u0000\u0000\u01bb\u01bd\u0007\u0019\u0000\u0000\u01bc\u01bb\u0001"+ + "\u0000\u0000\u0000\u01bd\u01c0\u0001\u0000\u0000\u0000\u01be\u01bc\u0001"+ + "\u0000\u0000\u0000\u01be\u01bf\u0001\u0000\u0000\u0000\u01bf\u01c6\u0001"+ + "\u0000\u0000\u0000\u01c0\u01be\u0001\u0000\u0000\u0000\u01c1\u01c2\u0005"+ + ":\u0000\u0000\u01c2\u01c7\u0005-\u0000\u0000\u01c3\u01c4\u0005:\u0000"+ + "\u0000\u01c4\u01c7\u0005+\u0000\u0000\u01c5\u01c7\u0005:\u0000\u0000\u01c6"+ + "\u01c1\u0001\u0000\u0000\u0000\u01c6\u01c3\u0001\u0000\u0000\u0000\u01c6"+ + "\u01c5\u0001\u0000\u0000\u0000\u01c6\u01c7\u0001\u0000\u0000\u0000\u01c7"+ + "\u01cb\u0001\u0000\u0000\u0000\u01c8\u01ca\b$\u0000\u0000\u01c9\u01c8"+ + "\u0001\u0000\u0000\u0000\u01ca\u01cd\u0001\u0000\u0000\u0000\u01cb\u01c9"+ + "\u0001\u0000\u0000\u0000\u01cb\u01cc\u0001\u0000\u0000\u0000\u01cc\u01ce"+ + "\u0001\u0000\u0000\u0000\u01cd\u01cb\u0001\u0000\u0000\u0000\u01ce\u01d8"+ + "\u0005}\u0000\u0000\u01cf\u01d0\u0005$\u0000\u0000\u01d0\u01d4\u0007\u0000"+ + "\u0000\u0000\u01d1\u01d3\u0007\u0019\u0000\u0000\u01d2\u01d1\u0001\u0000"+ + "\u0000\u0000\u01d3\u01d6\u0001\u0000\u0000\u0000\u01d4\u01d2\u0001\u0000"+ + "\u0000\u0000\u01d4\u01d5\u0001\u0000\u0000\u0000\u01d5\u01d8\u0001\u0000"+ + "\u0000\u0000\u01d6\u01d4\u0001\u0000\u0000\u0000\u01d7\u01b8\u0001\u0000"+ + "\u0000\u0000\u01d7\u01cf\u0001\u0000\u0000\u0000\u01d8\u01d9\u0001\u0000"+ + "\u0000\u0000\u01d9\u01da\u0006% \u0000\u01daN\u0001\u0000\u0000\u0000"+ + "\u01db\u01dc\u0005$\u0000\u0000\u01dc\u01dd\u0007%\u0000\u0000\u01dd\u01de"+ + "\u0006&!\u0000\u01deP\u0001\u0000\u0000\u0000\u01df\u01e0\u0005$\u0000"+ + "\u0000\u01e0\u01e1\u0005(\u0000\u0000\u01e1\u01ee\u0001\u0000\u0000\u0000"+ + "\u01e2\u01ed\u0003Q\'\u0000\u01e3\u01ed\b&\u0000\u0000\u01e4\u01e8\u0005"+ + "(\u0000\u0000\u01e5\u01e7\u0003S(\u0000\u01e6\u01e5\u0001\u0000\u0000"+ + "\u0000\u01e7\u01ea\u0001\u0000\u0000\u0000\u01e8\u01e6\u0001\u0000\u0000"+ + "\u0000\u01e8\u01e9\u0001\u0000\u0000\u0000\u01e9\u01eb\u0001\u0000\u0000"+ + "\u0000\u01ea\u01e8\u0001\u0000\u0000\u0000\u01eb\u01ed\u0005)\u0000\u0000"+ + "\u01ec\u01e2\u0001\u0000\u0000\u0000\u01ec\u01e3\u0001\u0000\u0000\u0000"+ + "\u01ec\u01e4\u0001\u0000\u0000\u0000\u01ed\u01f0\u0001\u0000\u0000\u0000"+ + "\u01ee\u01ec\u0001\u0000\u0000\u0000\u01ee\u01ef\u0001\u0000\u0000\u0000"+ + "\u01ef\u01f1\u0001\u0000\u0000\u0000\u01f0\u01ee\u0001\u0000\u0000\u0000"+ + "\u01f1\u01f2\u0005)\u0000\u0000\u01f2\u01f3\u0006\'\"\u0000\u01f3R\u0001"+ + "\u0000\u0000\u0000\u01f4\u01f7\u0003Q\'\u0000\u01f5\u01f7\b&\u0000\u0000"+ + "\u01f6\u01f4\u0001\u0000\u0000\u0000\u01f6\u01f5\u0001\u0000\u0000\u0000"+ + "\u01f7T\u0001\u0000\u0000\u0000\u01f8\u01f9\u0005`\u0000\u0000\u01f9\u01fd"+ + "\b\'\u0000\u0000\u01fa\u01fc\b \u0000\u0000\u01fb\u01fa\u0001\u0000\u0000"+ + "\u0000\u01fc\u01ff\u0001\u0000\u0000\u0000\u01fd\u01fb\u0001\u0000\u0000"+ + "\u0000\u01fd\u01fe\u0001\u0000\u0000\u0000\u01fe\u0200\u0001\u0000\u0000"+ + "\u0000\u01ff\u01fd\u0001\u0000\u0000\u0000\u0200\u0201\u0005`\u0000\u0000"+ + "\u0201\u0202\u0006)#\u0000\u0202V\u0001\u0000\u0000\u0000\u0203\u0208"+ + "\b(\u0000\u0000\u0204\u0207\u0003?\u001e\u0000\u0205\u0207\u0003A\u001f"+ + "\u0000\u0206\u0204\u0001\u0000\u0000\u0000\u0206\u0205\u0001\u0000\u0000"+ + "\u0000\u0207\u020a\u0001\u0000\u0000\u0000\u0208\u0206\u0001\u0000\u0000"+ + "\u0000\u0208\u0209\u0001\u0000\u0000\u0000\u0209\u0228\u0001\u0000\u0000"+ + "\u0000\u020a\u0208\u0001\u0000\u0000\u0000\u020b\u020c\u0005-\u0000\u0000"+ + "\u020c\u0211\b(\u0000\u0000\u020d\u0210\u0003?\u001e\u0000\u020e\u0210"+ + "\u0003A\u001f\u0000\u020f\u020d\u0001\u0000\u0000\u0000\u020f\u020e\u0001"+ + "\u0000\u0000\u0000\u0210\u0213\u0001\u0000\u0000\u0000\u0211\u020f\u0001"+ + "\u0000\u0000\u0000\u0211\u0212\u0001\u0000\u0000\u0000\u0212\u0228\u0001"+ + "\u0000\u0000\u0000\u0213\u0211\u0001\u0000\u0000\u0000\u0214\u0228\u0005"+ + "-\u0000\u0000\u0215\u0216\u0005<\u0000\u0000\u0216\u021b\b\u001f\u0000"+ + "\u0000\u0217\u021a\u0003?\u001e\u0000\u0218\u021a\u0003A\u001f\u0000\u0219"+ + "\u0217\u0001\u0000\u0000\u0000\u0219\u0218\u0001\u0000\u0000\u0000\u021a"+ + "\u021d\u0001\u0000\u0000\u0000\u021b\u0219\u0001\u0000\u0000\u0000\u021b"+ + "\u021c\u0001\u0000\u0000\u0000\u021c\u0228\u0001\u0000\u0000\u0000\u021d"+ + "\u021b\u0001\u0000\u0000\u0000\u021e\u0228\u0005<\u0000\u0000\u021f\u0224"+ + "\u0003A\u001f\u0000\u0220\u0223\u0003?\u001e\u0000\u0221\u0223\u0003A"+ + "\u001f\u0000\u0222\u0220\u0001\u0000\u0000\u0000\u0222\u0221\u0001\u0000"+ + "\u0000\u0000\u0223\u0226\u0001\u0000\u0000\u0000\u0224\u0222\u0001\u0000"+ + "\u0000\u0000\u0224\u0225\u0001\u0000\u0000\u0000\u0225\u0228\u0001\u0000"+ + "\u0000\u0000\u0226\u0224\u0001\u0000\u0000\u0000\u0227\u0203\u0001\u0000"+ + "\u0000\u0000\u0227\u020b\u0001\u0000\u0000\u0000\u0227\u0214\u0001\u0000"+ + "\u0000\u0000\u0227\u0215\u0001\u0000\u0000\u0000\u0227\u021e\u0001\u0000"+ + "\u0000\u0000\u0227\u021f\u0001\u0000\u0000\u0000\u0228\u0229\u0001\u0000"+ + "\u0000\u0000\u0229\u022a\u0006*$\u0000\u022aX\u0001\u0000\u0000\u0000"+ + "\u022b\u022d\u0003[,\u0000\u022c\u022b\u0001\u0000\u0000\u0000\u022d\u022e"+ + "\u0001\u0000\u0000\u0000\u022e\u022c\u0001\u0000\u0000\u0000\u022e\u022f"+ + "\u0001\u0000\u0000\u0000\u022f\u0230\u0001\u0000\u0000\u0000\u0230\u0231"+ + "\u0006+\u0001\u0000\u0231Z\u0001\u0000\u0000\u0000\u0232\u0233\u0007\u001b"+ + "\u0000\u0000\u0233\\\u0001\u0000\u0000\u0000\u0234\u0236\u0003_.\u0000"+ + "\u0235\u0234\u0001\u0000\u0000\u0000\u0236\u0237\u0001\u0000\u0000\u0000"+ + "\u0237\u0235\u0001\u0000\u0000\u0000\u0237\u0238\u0001\u0000\u0000\u0000"+ + "\u0238\u0239\u0001\u0000\u0000\u0000\u0239\u023a\u0006-%\u0000\u023a\u023b"+ + "\u0001\u0000\u0000\u0000\u023b\u023c\u0006-\u0001\u0000\u023c^\u0001\u0000"+ + "\u0000\u0000\u023d\u023e\u0007\u0001\u0000\u0000\u023e`\u0001\u0000\u0000"+ + "\u0000\u023f\u0243\u0007\u001a\u0000\u0000\u0240\u0242\u0007\u001b\u0000"+ + "\u0000\u0241\u0240\u0001\u0000\u0000\u0000\u0242\u0245\u0001\u0000\u0000"+ + "\u0000\u0243\u0241\u0001\u0000\u0000\u0000\u0243\u0244\u0001\u0000\u0000"+ + "\u0000\u0244\u0246\u0001\u0000\u0000\u0000\u0245\u0243\u0001\u0000\u0000"+ + "\u0000\u0246\u0247\u0005\n\u0000\u0000\u0247\u0248\u0001\u0000\u0000\u0000"+ + "\u0248\u0249\u0006/\u0001\u0000\u0249b\u0001\u0000\u0000\u0000\u024a\u024b"+ + "\u0005\n\u0000\u0000\u024b\u024c\u0001\u0000\u0000\u0000\u024c\u024d\u0006"+ + "0&\u0000\u024d\u024e\u00060\'\u0000\u024ed\u0001\u0000\u0000\u0000\u024f"+ + "\u0251\u0007)\u0000\u0000\u0250\u024f\u0001\u0000\u0000\u0000\u0251\u0252"+ + "\u0001\u0000\u0000\u0000\u0252\u0250\u0001\u0000\u0000\u0000\u0252\u0253"+ + "\u0001\u0000\u0000\u0000\u0253\u0254\u0001\u0000\u0000\u0000\u0254\u0255"+ + "\u00061\u0001\u0000\u0255f\u0001\u0000\u0000\u0000\u0256\u0257\u0005/"+ + "\u0000\u0000\u0257\u0258\u0005*\u0000\u0000\u0258\u025c\u0001\u0000\u0000"+ + "\u0000\u0259\u025b\t\u0000\u0000\u0000\u025a\u0259\u0001\u0000\u0000\u0000"+ + "\u025b\u025e\u0001\u0000\u0000\u0000\u025c\u025d\u0001\u0000\u0000\u0000"+ + "\u025c\u025a\u0001\u0000\u0000\u0000\u025d\u025f\u0001\u0000\u0000\u0000"+ + "\u025e\u025c\u0001\u0000\u0000\u0000\u025f\u0260\u0005*\u0000\u0000\u0260"+ + "\u0261\u0005/\u0000\u0000\u0261\u0262\u0001\u0000\u0000\u0000\u0262\u0263"+ + "\u00062\u0001\u0000\u0263h\u0001\u0000\u0000\u0000\u0264\u0265\u0005/"+ + "\u0000\u0000\u0265\u0268\u0005/\u0000\u0000\u0266\u0268\u0005#\u0000\u0000"+ + "\u0267\u0264\u0001\u0000\u0000\u0000\u0267\u0266\u0001\u0000\u0000\u0000"+ + "\u0268\u026c\u0001\u0000\u0000\u0000\u0269\u026b\b\u0001\u0000\u0000\u026a"+ + "\u0269\u0001\u0000\u0000\u0000\u026b\u026e\u0001\u0000\u0000\u0000\u026c"+ + "\u026a\u0001\u0000\u0000\u0000\u026c\u026d\u0001\u0000\u0000\u0000\u026d"+ + "\u0270\u0001\u0000\u0000\u0000\u026e\u026c\u0001\u0000\u0000\u0000\u026f"+ + "\u0271\u0005\r\u0000\u0000\u0270\u026f\u0001\u0000\u0000\u0000\u0270\u0271"+ + "\u0001\u0000\u0000\u0000\u0271\u0272\u0001\u0000\u0000\u0000\u0272\u0273"+ + "\u00063\u0001\u0000\u0273j\u0001\u0000\u0000\u0000\u0274\u0275\u0005<"+ + "\u0000\u0000\u0275\u0276\u0005<\u0000\u0000\u0276\u0278\u0001\u0000\u0000"+ + "\u0000\u0277\u0279\u0005-\u0000\u0000\u0278\u0277\u0001\u0000\u0000\u0000"+ + "\u0278\u0279\u0001\u0000\u0000\u0000\u0279\u027a\u0001\u0000\u0000\u0000"+ + "\u027a\u027e\u0007\u0000\u0000\u0000\u027b\u027d\u0007\u0019\u0000\u0000"+ + "\u027c\u027b\u0001\u0000\u0000\u0000\u027d\u0280\u0001\u0000\u0000\u0000"+ + "\u027e\u027c\u0001\u0000\u0000\u0000\u027e\u027f\u0001\u0000\u0000\u0000"+ + "\u027f\u0281\u0001\u0000\u0000\u0000\u0280\u027e\u0001\u0000\u0000\u0000"+ + "\u0281\u0282\u00064(\u0000\u0282\u0283\u0001\u0000\u0000\u0000\u0283\u0284"+ + "\u00064)\u0000\u0284l\u0001\u0000\u0000\u0000\u0285\u0287\b*\u0000\u0000"+ + "\u0286\u0285\u0001\u0000\u0000\u0000\u0287\u0288\u0001\u0000\u0000\u0000"+ + "\u0288\u0286\u0001\u0000\u0000\u0000\u0288\u0289\u0001\u0000\u0000\u0000"+ + "\u0289\u0294\u0001\u0000\u0000\u0000\u028a\u028b\u0005<\u0000\u0000\u028b"+ + "\u028f\b+\u0000\u0000\u028c\u028e\b\u001e\u0000\u0000\u028d\u028c\u0001"+ + "\u0000\u0000\u0000\u028e\u0291\u0001\u0000\u0000\u0000\u028f\u028d\u0001"+ + "\u0000\u0000\u0000\u028f\u0290\u0001\u0000\u0000\u0000\u0290\u0294\u0001"+ + "\u0000\u0000\u0000\u0291\u028f\u0001\u0000\u0000\u0000\u0292\u0294\u0005"+ + "<\u0000\u0000\u0293\u0286\u0001\u0000\u0000\u0000\u0293\u028a\u0001\u0000"+ + "\u0000\u0000\u0293\u0292\u0001\u0000\u0000\u0000\u0294\u0295\u0001\u0000"+ + "\u0000\u0000\u0295\u0296\u00065*\u0000\u0296n\u0001\u0000\u0000\u0000"+ + "\u0297\u0298\u0005\n\u0000\u0000\u0298\u0299\u0001\u0000\u0000\u0000\u0299"+ + "\u029a\u00066&\u0000\u029ap\u0001\u0000\u0000\u0000\u029b\u029d\b,\u0000"+ + "\u0000\u029c\u029b\u0001\u0000\u0000\u0000\u029d\u029e\u0001\u0000\u0000"+ + "\u0000\u029e\u029c\u0001\u0000\u0000\u0000\u029e\u029f\u0001\u0000\u0000"+ + "\u0000\u029f\u02a0\u0001\u0000\u0000\u0000\u02a0\u02a1\u00067+\u0000\u02a1"+ + "r\u0001\u0000\u0000\u00006\u0000\u0001\u0002w}\u0082\u0089\u008f\u0099"+ + "\u014c\u0152\u015d\u0177\u017e\u0180\u0193\u0195\u019f\u01a1\u01ab\u01b1"+ + "\u01be\u01c6\u01cb\u01d4\u01d7\u01e8\u01ec\u01ee\u01f6\u01fd\u0206\u0208"+ + "\u020f\u0211\u0219\u021b\u0222\u0224\u0227\u022e\u0237\u0243\u0252\u025c"+ + "\u0267\u026c\u0270\u0278\u027e\u0288\u028f\u0293\u029e,\u0001\u0000\u0000"+ + "\u0000\u0001\u0000\u0001\u0002\u0001\u0001\u0003\u0002\u0001\u0004\u0003"+ + "\u0001\u0005\u0004\u0001\u0006\u0005\u0001\u0007\u0006\u0001\b\u0007\u0001"+ + "\t\b\u0001\n\t\u0001\u000b\n\u0001\f\u000b\u0001\r\f\u0001\u000e\r\u0001"+ + "\u000f\u000e\u0001\u0010\u000f\u0001\u0011\u0010\u0001\u0012\u0011\u0001"+ + "\u0013\u0012\u0001\u0014\u0013\u0001\u0015\u0014\u0001\u0016\u0015\u0005"+ + "\u0001\u0000\u0001\u0018\u0016\u0001\u0019\u0017\u0001\u001a\u0018\u0001"+ + "\u001b\u0019\u0001\u001c\u001a\u0001\u001d\u001b\u0001 \u001c\u0001!\u001d"+ + "\u0001%\u001e\u0001&\u001f\u0001\' \u0001)!\u0001*\"\u0001-#\u0007\'\u0000"+ + "\u0002\u0002\u0000\u00014$\u0007\u0017\u0000\u0007%\u0000\u00017%"; + public static final ATN _ATN = + new ATNDeserializer().deserialize(_serializedATN.toCharArray()); + static { + _decisionToDFA = new DFA[_ATN.getNumberOfDecisions()]; + for (int i = 0; i < _ATN.getNumberOfDecisions(); i++) { + _decisionToDFA[i] = new DFA(_ATN.getDecisionState(i), i); + } + } +} \ No newline at end of file diff --git a/rewrite-docker/src/main/java/org/openrewrite/docker/internal/grammar/DockerLexer.tokens b/rewrite-docker/src/main/java/org/openrewrite/docker/internal/grammar/DockerLexer.tokens new file mode 100644 index 0000000000..8f8b4bd73c --- /dev/null +++ b/rewrite-docker/src/main/java/org/openrewrite/docker/internal/grammar/DockerLexer.tokens @@ -0,0 +1,71 @@ +PARSER_DIRECTIVE=1 +COMMENT=2 +FROM=3 +RUN=4 +CMD=5 +NONE=6 +LABEL=7 +EXPOSE=8 +ENV=9 +ADD=10 +COPY=11 +ENTRYPOINT=12 +VOLUME=13 +USER=14 +WORKDIR=15 +ARG=16 +ONBUILD=17 +STOPSIGNAL=18 +HEALTHCHECK=19 +SHELL=20 +MAINTAINER=21 +AS=22 +HEREDOC_START=23 +LINE_CONTINUATION=24 +LBRACKET=25 +RBRACKET=26 +COMMA=27 +EQUALS=28 +FLAG=29 +DASH_DASH=30 +DOUBLE_QUOTED_STRING=31 +SINGLE_QUOTED_STRING=32 +ENV_VAR=33 +SPECIAL_VAR=34 +COMMAND_SUBST=35 +BACKTICK_SUBST=36 +UNQUOTED_TEXT=37 +WS=38 +NEWLINE=39 +HP_LINE_CONTINUATION=40 +HP_WS=41 +HP_COMMENT=42 +HP_LINE_COMMENT=43 +HEREDOC_CONTENT=44 +H_NEWLINE=45 +'FROM'=3 +'RUN'=4 +'CMD'=5 +'NONE'=6 +'LABEL'=7 +'EXPOSE'=8 +'ENV'=9 +'ADD'=10 +'COPY'=11 +'ENTRYPOINT'=12 +'VOLUME'=13 +'USER'=14 +'WORKDIR'=15 +'ARG'=16 +'ONBUILD'=17 +'STOPSIGNAL'=18 +'HEALTHCHECK'=19 +'SHELL'=20 +'MAINTAINER'=21 +'AS'=22 +'['=25 +']'=26 +','=27 +'='=28 +'--'=30 +'\n'=45 diff --git a/rewrite-docker/src/main/java/org/openrewrite/docker/internal/grammar/DockerParser.interp b/rewrite-docker/src/main/java/org/openrewrite/docker/internal/grammar/DockerParser.interp new file mode 100644 index 0000000000..100a9718cc --- /dev/null +++ b/rewrite-docker/src/main/java/org/openrewrite/docker/internal/grammar/DockerParser.interp @@ -0,0 +1,172 @@ +token literal names: +null +null +null +'FROM' +'RUN' +'CMD' +'NONE' +'LABEL' +'EXPOSE' +'ENV' +'ADD' +'COPY' +'ENTRYPOINT' +'VOLUME' +'USER' +'WORKDIR' +'ARG' +'ONBUILD' +'STOPSIGNAL' +'HEALTHCHECK' +'SHELL' +'MAINTAINER' +'AS' +null +null +'[' +']' +',' +'=' +null +'--' +null +null +null +null +null +null +null +null +null +null +null +null +null +null +'\n' + +token symbolic names: +null +PARSER_DIRECTIVE +COMMENT +FROM +RUN +CMD +NONE +LABEL +EXPOSE +ENV +ADD +COPY +ENTRYPOINT +VOLUME +USER +WORKDIR +ARG +ONBUILD +STOPSIGNAL +HEALTHCHECK +SHELL +MAINTAINER +AS +HEREDOC_START +LINE_CONTINUATION +LBRACKET +RBRACKET +COMMA +EQUALS +FLAG +DASH_DASH +DOUBLE_QUOTED_STRING +SINGLE_QUOTED_STRING +ENV_VAR +SPECIAL_VAR +COMMAND_SUBST +BACKTICK_SUBST +UNQUOTED_TEXT +WS +NEWLINE +HP_LINE_CONTINUATION +HP_WS +HP_COMMENT +HP_LINE_COMMENT +HEREDOC_CONTENT +H_NEWLINE + +rule names: +dockerfile +parserDirective +globalArgs +stage +stageInstruction +instruction +fromInstruction +runInstruction +cmdInstruction +labelInstruction +exposeInstruction +envInstruction +addInstruction +copyInstruction +entrypointInstruction +volumeInstruction +userInstruction +workdirInstruction +argInstruction +onbuildInstruction +stopsignalInstruction +healthcheckInstruction +healthcheckOptions +healthcheckOption +shellInstruction +maintainerInstruction +flags +flag +execForm +shellForm +shellFormText +shellFormTextElement +heredoc +heredocPreamble +preambleElement +heredocBody +heredocContent +heredocEnd +jsonArray +jsonArrayElements +jsonString +imageName +stageName +labelPairs +labelPair +labelKey +labelValue +labelOldValue +labelOldValueElement +portList +port +envPairs +envPair +envKey +envValueEquals +envValueSpace +envTextEquals +envTextElementEquals +sourceList +sourcePath +destination +destinationPath +path +pathList +volumePath +userSpec +argName +argValue +signal +text +textElement + + +atn: +[4, 1, 45, 474, 2, 0, 7, 0, 2, 1, 7, 1, 2, 2, 7, 2, 2, 3, 7, 3, 2, 4, 7, 4, 2, 5, 7, 5, 2, 6, 7, 6, 2, 7, 7, 7, 2, 8, 7, 8, 2, 9, 7, 9, 2, 10, 7, 10, 2, 11, 7, 11, 2, 12, 7, 12, 2, 13, 7, 13, 2, 14, 7, 14, 2, 15, 7, 15, 2, 16, 7, 16, 2, 17, 7, 17, 2, 18, 7, 18, 2, 19, 7, 19, 2, 20, 7, 20, 2, 21, 7, 21, 2, 22, 7, 22, 2, 23, 7, 23, 2, 24, 7, 24, 2, 25, 7, 25, 2, 26, 7, 26, 2, 27, 7, 27, 2, 28, 7, 28, 2, 29, 7, 29, 2, 30, 7, 30, 2, 31, 7, 31, 2, 32, 7, 32, 2, 33, 7, 33, 2, 34, 7, 34, 2, 35, 7, 35, 2, 36, 7, 36, 2, 37, 7, 37, 2, 38, 7, 38, 2, 39, 7, 39, 2, 40, 7, 40, 2, 41, 7, 41, 2, 42, 7, 42, 2, 43, 7, 43, 2, 44, 7, 44, 2, 45, 7, 45, 2, 46, 7, 46, 2, 47, 7, 47, 2, 48, 7, 48, 2, 49, 7, 49, 2, 50, 7, 50, 2, 51, 7, 51, 2, 52, 7, 52, 2, 53, 7, 53, 2, 54, 7, 54, 2, 55, 7, 55, 2, 56, 7, 56, 2, 57, 7, 57, 2, 58, 7, 58, 2, 59, 7, 59, 2, 60, 7, 60, 2, 61, 7, 61, 2, 62, 7, 62, 2, 63, 7, 63, 2, 64, 7, 64, 2, 65, 7, 65, 2, 66, 7, 66, 2, 67, 7, 67, 2, 68, 7, 68, 2, 69, 7, 69, 2, 70, 7, 70, 1, 0, 5, 0, 144, 8, 0, 10, 0, 12, 0, 147, 9, 0, 1, 0, 1, 0, 4, 0, 151, 8, 0, 11, 0, 12, 0, 152, 1, 0, 1, 0, 1, 1, 1, 1, 1, 2, 5, 2, 160, 8, 2, 10, 2, 12, 2, 163, 9, 2, 1, 3, 1, 3, 5, 3, 167, 8, 3, 10, 3, 12, 3, 170, 9, 3, 1, 4, 1, 4, 1, 4, 1, 4, 1, 4, 1, 4, 1, 4, 1, 4, 1, 4, 1, 4, 1, 4, 1, 4, 1, 4, 1, 4, 1, 4, 1, 4, 1, 4, 3, 4, 189, 8, 4, 1, 5, 1, 5, 3, 5, 193, 8, 5, 1, 6, 1, 6, 3, 6, 197, 8, 6, 1, 6, 1, 6, 1, 6, 3, 6, 202, 8, 6, 1, 7, 1, 7, 3, 7, 206, 8, 7, 1, 7, 1, 7, 1, 7, 3, 7, 211, 8, 7, 1, 8, 1, 8, 1, 8, 3, 8, 216, 8, 8, 1, 9, 1, 9, 1, 9, 1, 10, 1, 10, 1, 10, 1, 11, 1, 11, 1, 11, 1, 12, 1, 12, 3, 12, 229, 8, 12, 1, 12, 1, 12, 1, 12, 1, 12, 1, 12, 3, 12, 236, 8, 12, 1, 13, 1, 13, 3, 13, 240, 8, 13, 1, 13, 1, 13, 1, 13, 1, 13, 1, 13, 3, 13, 247, 8, 13, 1, 14, 1, 14, 1, 14, 3, 14, 252, 8, 14, 1, 15, 1, 15, 1, 15, 3, 15, 257, 8, 15, 1, 16, 1, 16, 1, 16, 1, 17, 1, 17, 1, 17, 1, 18, 1, 18, 1, 18, 1, 18, 3, 18, 269, 8, 18, 1, 19, 1, 19, 1, 19, 1, 20, 1, 20, 1, 20, 1, 21, 1, 21, 1, 21, 1, 21, 3, 21, 281, 8, 21, 1, 21, 1, 21, 1, 21, 3, 21, 286, 8, 21, 3, 21, 288, 8, 21, 1, 22, 4, 22, 291, 8, 22, 11, 22, 12, 22, 292, 1, 23, 1, 23, 1, 24, 1, 24, 1, 24, 1, 25, 1, 25, 1, 25, 1, 26, 4, 26, 304, 8, 26, 11, 26, 12, 26, 305, 1, 27, 1, 27, 1, 28, 1, 28, 1, 29, 1, 29, 1, 30, 4, 30, 315, 8, 30, 11, 30, 12, 30, 316, 1, 31, 1, 31, 1, 32, 1, 32, 1, 32, 4, 32, 324, 8, 32, 11, 32, 12, 32, 325, 1, 33, 1, 33, 5, 33, 330, 8, 33, 10, 33, 12, 33, 333, 9, 33, 1, 33, 1, 33, 5, 33, 337, 8, 33, 10, 33, 12, 33, 340, 9, 33, 5, 33, 342, 8, 33, 10, 33, 12, 33, 345, 9, 33, 1, 34, 1, 34, 1, 35, 1, 35, 1, 35, 1, 36, 5, 36, 353, 8, 36, 10, 36, 12, 36, 356, 9, 36, 1, 37, 1, 37, 1, 38, 1, 38, 3, 38, 362, 8, 38, 1, 38, 1, 38, 1, 39, 1, 39, 1, 39, 5, 39, 369, 8, 39, 10, 39, 12, 39, 372, 9, 39, 1, 40, 1, 40, 1, 41, 1, 41, 1, 42, 1, 42, 1, 43, 4, 43, 381, 8, 43, 11, 43, 12, 43, 382, 1, 44, 1, 44, 1, 44, 1, 44, 1, 44, 1, 44, 1, 44, 3, 44, 392, 8, 44, 1, 45, 1, 45, 1, 46, 1, 46, 1, 47, 4, 47, 399, 8, 47, 11, 47, 12, 47, 400, 1, 48, 1, 48, 1, 49, 4, 49, 406, 8, 49, 11, 49, 12, 49, 407, 1, 50, 1, 50, 1, 51, 4, 51, 413, 8, 51, 11, 51, 12, 51, 414, 1, 52, 1, 52, 1, 52, 1, 52, 1, 52, 1, 52, 1, 52, 3, 52, 424, 8, 52, 1, 53, 1, 53, 1, 54, 1, 54, 1, 55, 1, 55, 1, 56, 4, 56, 433, 8, 56, 11, 56, 12, 56, 434, 1, 57, 1, 57, 1, 58, 4, 58, 440, 8, 58, 11, 58, 12, 58, 441, 1, 59, 1, 59, 1, 60, 1, 60, 1, 61, 1, 61, 1, 62, 1, 62, 1, 63, 4, 63, 453, 8, 63, 11, 63, 12, 63, 454, 1, 64, 1, 64, 1, 65, 1, 65, 1, 66, 1, 66, 1, 67, 1, 67, 1, 68, 1, 68, 1, 69, 4, 69, 468, 8, 69, 11, 69, 12, 69, 469, 1, 70, 1, 70, 1, 70, 0, 0, 71, 0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32, 34, 36, 38, 40, 42, 44, 46, 48, 50, 52, 54, 56, 58, 60, 62, 64, 66, 68, 70, 72, 74, 76, 78, 80, 82, 84, 86, 88, 90, 92, 94, 96, 98, 100, 102, 104, 106, 108, 110, 112, 114, 116, 118, 120, 122, 124, 126, 128, 130, 132, 134, 136, 138, 140, 0, 5, 1, 0, 25, 37, 2, 0, 39, 39, 44, 44, 2, 0, 31, 32, 37, 37, 1, 0, 33, 37, 1, 0, 31, 37, 461, 0, 145, 1, 0, 0, 0, 2, 156, 1, 0, 0, 0, 4, 161, 1, 0, 0, 0, 6, 164, 1, 0, 0, 0, 8, 188, 1, 0, 0, 0, 10, 192, 1, 0, 0, 0, 12, 194, 1, 0, 0, 0, 14, 203, 1, 0, 0, 0, 16, 212, 1, 0, 0, 0, 18, 217, 1, 0, 0, 0, 20, 220, 1, 0, 0, 0, 22, 223, 1, 0, 0, 0, 24, 226, 1, 0, 0, 0, 26, 237, 1, 0, 0, 0, 28, 248, 1, 0, 0, 0, 30, 253, 1, 0, 0, 0, 32, 258, 1, 0, 0, 0, 34, 261, 1, 0, 0, 0, 36, 264, 1, 0, 0, 0, 38, 270, 1, 0, 0, 0, 40, 273, 1, 0, 0, 0, 42, 287, 1, 0, 0, 0, 44, 290, 1, 0, 0, 0, 46, 294, 1, 0, 0, 0, 48, 296, 1, 0, 0, 0, 50, 299, 1, 0, 0, 0, 52, 303, 1, 0, 0, 0, 54, 307, 1, 0, 0, 0, 56, 309, 1, 0, 0, 0, 58, 311, 1, 0, 0, 0, 60, 314, 1, 0, 0, 0, 62, 318, 1, 0, 0, 0, 64, 320, 1, 0, 0, 0, 66, 327, 1, 0, 0, 0, 68, 346, 1, 0, 0, 0, 70, 348, 1, 0, 0, 0, 72, 354, 1, 0, 0, 0, 74, 357, 1, 0, 0, 0, 76, 359, 1, 0, 0, 0, 78, 365, 1, 0, 0, 0, 80, 373, 1, 0, 0, 0, 82, 375, 1, 0, 0, 0, 84, 377, 1, 0, 0, 0, 86, 380, 1, 0, 0, 0, 88, 391, 1, 0, 0, 0, 90, 393, 1, 0, 0, 0, 92, 395, 1, 0, 0, 0, 94, 398, 1, 0, 0, 0, 96, 402, 1, 0, 0, 0, 98, 405, 1, 0, 0, 0, 100, 409, 1, 0, 0, 0, 102, 412, 1, 0, 0, 0, 104, 423, 1, 0, 0, 0, 106, 425, 1, 0, 0, 0, 108, 427, 1, 0, 0, 0, 110, 429, 1, 0, 0, 0, 112, 432, 1, 0, 0, 0, 114, 436, 1, 0, 0, 0, 116, 439, 1, 0, 0, 0, 118, 443, 1, 0, 0, 0, 120, 445, 1, 0, 0, 0, 122, 447, 1, 0, 0, 0, 124, 449, 1, 0, 0, 0, 126, 452, 1, 0, 0, 0, 128, 456, 1, 0, 0, 0, 130, 458, 1, 0, 0, 0, 132, 460, 1, 0, 0, 0, 134, 462, 1, 0, 0, 0, 136, 464, 1, 0, 0, 0, 138, 467, 1, 0, 0, 0, 140, 471, 1, 0, 0, 0, 142, 144, 3, 2, 1, 0, 143, 142, 1, 0, 0, 0, 144, 147, 1, 0, 0, 0, 145, 143, 1, 0, 0, 0, 145, 146, 1, 0, 0, 0, 146, 148, 1, 0, 0, 0, 147, 145, 1, 0, 0, 0, 148, 150, 3, 4, 2, 0, 149, 151, 3, 6, 3, 0, 150, 149, 1, 0, 0, 0, 151, 152, 1, 0, 0, 0, 152, 150, 1, 0, 0, 0, 152, 153, 1, 0, 0, 0, 153, 154, 1, 0, 0, 0, 154, 155, 5, 0, 0, 1, 155, 1, 1, 0, 0, 0, 156, 157, 5, 1, 0, 0, 157, 3, 1, 0, 0, 0, 158, 160, 3, 36, 18, 0, 159, 158, 1, 0, 0, 0, 160, 163, 1, 0, 0, 0, 161, 159, 1, 0, 0, 0, 161, 162, 1, 0, 0, 0, 162, 5, 1, 0, 0, 0, 163, 161, 1, 0, 0, 0, 164, 168, 3, 12, 6, 0, 165, 167, 3, 8, 4, 0, 166, 165, 1, 0, 0, 0, 167, 170, 1, 0, 0, 0, 168, 166, 1, 0, 0, 0, 168, 169, 1, 0, 0, 0, 169, 7, 1, 0, 0, 0, 170, 168, 1, 0, 0, 0, 171, 189, 3, 14, 7, 0, 172, 189, 3, 16, 8, 0, 173, 189, 3, 18, 9, 0, 174, 189, 3, 20, 10, 0, 175, 189, 3, 22, 11, 0, 176, 189, 3, 24, 12, 0, 177, 189, 3, 26, 13, 0, 178, 189, 3, 28, 14, 0, 179, 189, 3, 30, 15, 0, 180, 189, 3, 32, 16, 0, 181, 189, 3, 34, 17, 0, 182, 189, 3, 36, 18, 0, 183, 189, 3, 38, 19, 0, 184, 189, 3, 40, 20, 0, 185, 189, 3, 42, 21, 0, 186, 189, 3, 48, 24, 0, 187, 189, 3, 50, 25, 0, 188, 171, 1, 0, 0, 0, 188, 172, 1, 0, 0, 0, 188, 173, 1, 0, 0, 0, 188, 174, 1, 0, 0, 0, 188, 175, 1, 0, 0, 0, 188, 176, 1, 0, 0, 0, 188, 177, 1, 0, 0, 0, 188, 178, 1, 0, 0, 0, 188, 179, 1, 0, 0, 0, 188, 180, 1, 0, 0, 0, 188, 181, 1, 0, 0, 0, 188, 182, 1, 0, 0, 0, 188, 183, 1, 0, 0, 0, 188, 184, 1, 0, 0, 0, 188, 185, 1, 0, 0, 0, 188, 186, 1, 0, 0, 0, 188, 187, 1, 0, 0, 0, 189, 9, 1, 0, 0, 0, 190, 193, 3, 12, 6, 0, 191, 193, 3, 8, 4, 0, 192, 190, 1, 0, 0, 0, 192, 191, 1, 0, 0, 0, 193, 11, 1, 0, 0, 0, 194, 196, 5, 3, 0, 0, 195, 197, 3, 52, 26, 0, 196, 195, 1, 0, 0, 0, 196, 197, 1, 0, 0, 0, 197, 198, 1, 0, 0, 0, 198, 201, 3, 82, 41, 0, 199, 200, 5, 22, 0, 0, 200, 202, 3, 84, 42, 0, 201, 199, 1, 0, 0, 0, 201, 202, 1, 0, 0, 0, 202, 13, 1, 0, 0, 0, 203, 205, 5, 4, 0, 0, 204, 206, 3, 52, 26, 0, 205, 204, 1, 0, 0, 0, 205, 206, 1, 0, 0, 0, 206, 210, 1, 0, 0, 0, 207, 211, 3, 56, 28, 0, 208, 211, 3, 58, 29, 0, 209, 211, 3, 64, 32, 0, 210, 207, 1, 0, 0, 0, 210, 208, 1, 0, 0, 0, 210, 209, 1, 0, 0, 0, 211, 15, 1, 0, 0, 0, 212, 215, 5, 5, 0, 0, 213, 216, 3, 56, 28, 0, 214, 216, 3, 58, 29, 0, 215, 213, 1, 0, 0, 0, 215, 214, 1, 0, 0, 0, 216, 17, 1, 0, 0, 0, 217, 218, 5, 7, 0, 0, 218, 219, 3, 86, 43, 0, 219, 19, 1, 0, 0, 0, 220, 221, 5, 8, 0, 0, 221, 222, 3, 98, 49, 0, 222, 21, 1, 0, 0, 0, 223, 224, 5, 9, 0, 0, 224, 225, 3, 102, 51, 0, 225, 23, 1, 0, 0, 0, 226, 228, 5, 10, 0, 0, 227, 229, 3, 52, 26, 0, 228, 227, 1, 0, 0, 0, 228, 229, 1, 0, 0, 0, 229, 235, 1, 0, 0, 0, 230, 236, 3, 64, 32, 0, 231, 236, 3, 76, 38, 0, 232, 233, 3, 116, 58, 0, 233, 234, 3, 120, 60, 0, 234, 236, 1, 0, 0, 0, 235, 230, 1, 0, 0, 0, 235, 231, 1, 0, 0, 0, 235, 232, 1, 0, 0, 0, 236, 25, 1, 0, 0, 0, 237, 239, 5, 11, 0, 0, 238, 240, 3, 52, 26, 0, 239, 238, 1, 0, 0, 0, 239, 240, 1, 0, 0, 0, 240, 246, 1, 0, 0, 0, 241, 247, 3, 64, 32, 0, 242, 247, 3, 76, 38, 0, 243, 244, 3, 116, 58, 0, 244, 245, 3, 120, 60, 0, 245, 247, 1, 0, 0, 0, 246, 241, 1, 0, 0, 0, 246, 242, 1, 0, 0, 0, 246, 243, 1, 0, 0, 0, 247, 27, 1, 0, 0, 0, 248, 251, 5, 12, 0, 0, 249, 252, 3, 56, 28, 0, 250, 252, 3, 58, 29, 0, 251, 249, 1, 0, 0, 0, 251, 250, 1, 0, 0, 0, 252, 29, 1, 0, 0, 0, 253, 256, 5, 13, 0, 0, 254, 257, 3, 76, 38, 0, 255, 257, 3, 126, 63, 0, 256, 254, 1, 0, 0, 0, 256, 255, 1, 0, 0, 0, 257, 31, 1, 0, 0, 0, 258, 259, 5, 14, 0, 0, 259, 260, 3, 130, 65, 0, 260, 33, 1, 0, 0, 0, 261, 262, 5, 15, 0, 0, 262, 263, 3, 124, 62, 0, 263, 35, 1, 0, 0, 0, 264, 265, 5, 16, 0, 0, 265, 268, 3, 132, 66, 0, 266, 267, 5, 28, 0, 0, 267, 269, 3, 134, 67, 0, 268, 266, 1, 0, 0, 0, 268, 269, 1, 0, 0, 0, 269, 37, 1, 0, 0, 0, 270, 271, 5, 17, 0, 0, 271, 272, 3, 10, 5, 0, 272, 39, 1, 0, 0, 0, 273, 274, 5, 18, 0, 0, 274, 275, 3, 136, 68, 0, 275, 41, 1, 0, 0, 0, 276, 277, 5, 19, 0, 0, 277, 288, 5, 6, 0, 0, 278, 280, 5, 19, 0, 0, 279, 281, 3, 44, 22, 0, 280, 279, 1, 0, 0, 0, 280, 281, 1, 0, 0, 0, 281, 282, 1, 0, 0, 0, 282, 285, 5, 5, 0, 0, 283, 286, 3, 56, 28, 0, 284, 286, 3, 58, 29, 0, 285, 283, 1, 0, 0, 0, 285, 284, 1, 0, 0, 0, 286, 288, 1, 0, 0, 0, 287, 276, 1, 0, 0, 0, 287, 278, 1, 0, 0, 0, 288, 43, 1, 0, 0, 0, 289, 291, 3, 46, 23, 0, 290, 289, 1, 0, 0, 0, 291, 292, 1, 0, 0, 0, 292, 290, 1, 0, 0, 0, 292, 293, 1, 0, 0, 0, 293, 45, 1, 0, 0, 0, 294, 295, 5, 29, 0, 0, 295, 47, 1, 0, 0, 0, 296, 297, 5, 20, 0, 0, 297, 298, 3, 76, 38, 0, 298, 49, 1, 0, 0, 0, 299, 300, 5, 21, 0, 0, 300, 301, 3, 138, 69, 0, 301, 51, 1, 0, 0, 0, 302, 304, 3, 54, 27, 0, 303, 302, 1, 0, 0, 0, 304, 305, 1, 0, 0, 0, 305, 303, 1, 0, 0, 0, 305, 306, 1, 0, 0, 0, 306, 53, 1, 0, 0, 0, 307, 308, 5, 29, 0, 0, 308, 55, 1, 0, 0, 0, 309, 310, 3, 76, 38, 0, 310, 57, 1, 0, 0, 0, 311, 312, 3, 60, 30, 0, 312, 59, 1, 0, 0, 0, 313, 315, 3, 62, 31, 0, 314, 313, 1, 0, 0, 0, 315, 316, 1, 0, 0, 0, 316, 314, 1, 0, 0, 0, 316, 317, 1, 0, 0, 0, 317, 61, 1, 0, 0, 0, 318, 319, 7, 0, 0, 0, 319, 63, 1, 0, 0, 0, 320, 321, 3, 66, 33, 0, 321, 323, 5, 39, 0, 0, 322, 324, 3, 70, 35, 0, 323, 322, 1, 0, 0, 0, 324, 325, 1, 0, 0, 0, 325, 323, 1, 0, 0, 0, 325, 326, 1, 0, 0, 0, 326, 65, 1, 0, 0, 0, 327, 331, 5, 23, 0, 0, 328, 330, 3, 68, 34, 0, 329, 328, 1, 0, 0, 0, 330, 333, 1, 0, 0, 0, 331, 329, 1, 0, 0, 0, 331, 332, 1, 0, 0, 0, 332, 343, 1, 0, 0, 0, 333, 331, 1, 0, 0, 0, 334, 338, 5, 23, 0, 0, 335, 337, 3, 68, 34, 0, 336, 335, 1, 0, 0, 0, 337, 340, 1, 0, 0, 0, 338, 336, 1, 0, 0, 0, 338, 339, 1, 0, 0, 0, 339, 342, 1, 0, 0, 0, 340, 338, 1, 0, 0, 0, 341, 334, 1, 0, 0, 0, 342, 345, 1, 0, 0, 0, 343, 341, 1, 0, 0, 0, 343, 344, 1, 0, 0, 0, 344, 67, 1, 0, 0, 0, 345, 343, 1, 0, 0, 0, 346, 347, 7, 0, 0, 0, 347, 69, 1, 0, 0, 0, 348, 349, 3, 72, 36, 0, 349, 350, 3, 74, 37, 0, 350, 71, 1, 0, 0, 0, 351, 353, 7, 1, 0, 0, 352, 351, 1, 0, 0, 0, 353, 356, 1, 0, 0, 0, 354, 352, 1, 0, 0, 0, 354, 355, 1, 0, 0, 0, 355, 73, 1, 0, 0, 0, 356, 354, 1, 0, 0, 0, 357, 358, 5, 37, 0, 0, 358, 75, 1, 0, 0, 0, 359, 361, 5, 25, 0, 0, 360, 362, 3, 78, 39, 0, 361, 360, 1, 0, 0, 0, 361, 362, 1, 0, 0, 0, 362, 363, 1, 0, 0, 0, 363, 364, 5, 26, 0, 0, 364, 77, 1, 0, 0, 0, 365, 370, 3, 80, 40, 0, 366, 367, 5, 27, 0, 0, 367, 369, 3, 80, 40, 0, 368, 366, 1, 0, 0, 0, 369, 372, 1, 0, 0, 0, 370, 368, 1, 0, 0, 0, 370, 371, 1, 0, 0, 0, 371, 79, 1, 0, 0, 0, 372, 370, 1, 0, 0, 0, 373, 374, 5, 31, 0, 0, 374, 81, 1, 0, 0, 0, 375, 376, 3, 138, 69, 0, 376, 83, 1, 0, 0, 0, 377, 378, 5, 37, 0, 0, 378, 85, 1, 0, 0, 0, 379, 381, 3, 88, 44, 0, 380, 379, 1, 0, 0, 0, 381, 382, 1, 0, 0, 0, 382, 380, 1, 0, 0, 0, 382, 383, 1, 0, 0, 0, 383, 87, 1, 0, 0, 0, 384, 385, 3, 90, 45, 0, 385, 386, 5, 28, 0, 0, 386, 387, 3, 92, 46, 0, 387, 392, 1, 0, 0, 0, 388, 389, 3, 90, 45, 0, 389, 390, 3, 94, 47, 0, 390, 392, 1, 0, 0, 0, 391, 384, 1, 0, 0, 0, 391, 388, 1, 0, 0, 0, 392, 89, 1, 0, 0, 0, 393, 394, 7, 2, 0, 0, 394, 91, 1, 0, 0, 0, 395, 396, 7, 2, 0, 0, 396, 93, 1, 0, 0, 0, 397, 399, 3, 96, 48, 0, 398, 397, 1, 0, 0, 0, 399, 400, 1, 0, 0, 0, 400, 398, 1, 0, 0, 0, 400, 401, 1, 0, 0, 0, 401, 95, 1, 0, 0, 0, 402, 403, 7, 0, 0, 0, 403, 97, 1, 0, 0, 0, 404, 406, 3, 100, 50, 0, 405, 404, 1, 0, 0, 0, 406, 407, 1, 0, 0, 0, 407, 405, 1, 0, 0, 0, 407, 408, 1, 0, 0, 0, 408, 99, 1, 0, 0, 0, 409, 410, 7, 3, 0, 0, 410, 101, 1, 0, 0, 0, 411, 413, 3, 104, 52, 0, 412, 411, 1, 0, 0, 0, 413, 414, 1, 0, 0, 0, 414, 412, 1, 0, 0, 0, 414, 415, 1, 0, 0, 0, 415, 103, 1, 0, 0, 0, 416, 417, 3, 106, 53, 0, 417, 418, 5, 28, 0, 0, 418, 419, 3, 108, 54, 0, 419, 424, 1, 0, 0, 0, 420, 421, 3, 106, 53, 0, 421, 422, 3, 110, 55, 0, 422, 424, 1, 0, 0, 0, 423, 416, 1, 0, 0, 0, 423, 420, 1, 0, 0, 0, 424, 105, 1, 0, 0, 0, 425, 426, 5, 37, 0, 0, 426, 107, 1, 0, 0, 0, 427, 428, 3, 112, 56, 0, 428, 109, 1, 0, 0, 0, 429, 430, 3, 138, 69, 0, 430, 111, 1, 0, 0, 0, 431, 433, 3, 114, 57, 0, 432, 431, 1, 0, 0, 0, 433, 434, 1, 0, 0, 0, 434, 432, 1, 0, 0, 0, 434, 435, 1, 0, 0, 0, 435, 113, 1, 0, 0, 0, 436, 437, 7, 4, 0, 0, 437, 115, 1, 0, 0, 0, 438, 440, 3, 118, 59, 0, 439, 438, 1, 0, 0, 0, 440, 441, 1, 0, 0, 0, 441, 439, 1, 0, 0, 0, 441, 442, 1, 0, 0, 0, 442, 117, 1, 0, 0, 0, 443, 444, 7, 4, 0, 0, 444, 119, 1, 0, 0, 0, 445, 446, 3, 122, 61, 0, 446, 121, 1, 0, 0, 0, 447, 448, 7, 4, 0, 0, 448, 123, 1, 0, 0, 0, 449, 450, 3, 138, 69, 0, 450, 125, 1, 0, 0, 0, 451, 453, 3, 128, 64, 0, 452, 451, 1, 0, 0, 0, 453, 454, 1, 0, 0, 0, 454, 452, 1, 0, 0, 0, 454, 455, 1, 0, 0, 0, 455, 127, 1, 0, 0, 0, 456, 457, 7, 4, 0, 0, 457, 129, 1, 0, 0, 0, 458, 459, 3, 138, 69, 0, 459, 131, 1, 0, 0, 0, 460, 461, 5, 37, 0, 0, 461, 133, 1, 0, 0, 0, 462, 463, 3, 138, 69, 0, 463, 135, 1, 0, 0, 0, 464, 465, 5, 37, 0, 0, 465, 137, 1, 0, 0, 0, 466, 468, 3, 140, 70, 0, 467, 466, 1, 0, 0, 0, 468, 469, 1, 0, 0, 0, 469, 467, 1, 0, 0, 0, 469, 470, 1, 0, 0, 0, 470, 139, 1, 0, 0, 0, 471, 472, 7, 0, 0, 0, 472, 141, 1, 0, 0, 0, 41, 145, 152, 161, 168, 188, 192, 196, 201, 205, 210, 215, 228, 235, 239, 246, 251, 256, 268, 280, 285, 287, 292, 305, 316, 325, 331, 338, 343, 354, 361, 370, 382, 391, 400, 407, 414, 423, 434, 441, 454, 469] \ No newline at end of file diff --git a/rewrite-docker/src/main/java/org/openrewrite/docker/internal/grammar/DockerParser.java b/rewrite-docker/src/main/java/org/openrewrite/docker/internal/grammar/DockerParser.java new file mode 100644 index 0000000000..778005812b --- /dev/null +++ b/rewrite-docker/src/main/java/org/openrewrite/docker/internal/grammar/DockerParser.java @@ -0,0 +1,4826 @@ +/* + * Copyright 2026 the original author or authors. + *

+ * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * https://www.apache.org/licenses/LICENSE-2.0 + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +// Generated from /home/tim/Documents/workspace/openrewrite/rewrite/rewrite-docker/src/main/antlr/DockerParser.g4 by ANTLR 4.13.2 +package org.openrewrite.docker.internal.grammar; +import org.antlr.v4.runtime.atn.*; +import org.antlr.v4.runtime.dfa.DFA; +import org.antlr.v4.runtime.*; +import org.antlr.v4.runtime.misc.*; +import org.antlr.v4.runtime.tree.*; +import java.util.List; +import java.util.Iterator; +import java.util.ArrayList; + +@SuppressWarnings({"all", "warnings", "unchecked", "unused", "cast", "CheckReturnValue", "this-escape"}) +public class DockerParser extends Parser { + static { RuntimeMetaData.checkVersion("4.13.2", RuntimeMetaData.VERSION); } + + protected static final DFA[] _decisionToDFA; + protected static final PredictionContextCache _sharedContextCache = + new PredictionContextCache(); + public static final int + PARSER_DIRECTIVE=1, COMMENT=2, FROM=3, RUN=4, CMD=5, NONE=6, LABEL=7, + EXPOSE=8, ENV=9, ADD=10, COPY=11, ENTRYPOINT=12, VOLUME=13, USER=14, WORKDIR=15, + ARG=16, ONBUILD=17, STOPSIGNAL=18, HEALTHCHECK=19, SHELL=20, MAINTAINER=21, + AS=22, HEREDOC_START=23, LINE_CONTINUATION=24, LBRACKET=25, RBRACKET=26, + COMMA=27, EQUALS=28, FLAG=29, DASH_DASH=30, DOUBLE_QUOTED_STRING=31, SINGLE_QUOTED_STRING=32, + ENV_VAR=33, SPECIAL_VAR=34, COMMAND_SUBST=35, BACKTICK_SUBST=36, UNQUOTED_TEXT=37, + WS=38, NEWLINE=39, HP_LINE_CONTINUATION=40, HP_WS=41, HP_COMMENT=42, HP_LINE_COMMENT=43, + HEREDOC_CONTENT=44, H_NEWLINE=45; + public static final int + RULE_dockerfile = 0, RULE_parserDirective = 1, RULE_globalArgs = 2, RULE_stage = 3, + RULE_stageInstruction = 4, RULE_instruction = 5, RULE_fromInstruction = 6, + RULE_runInstruction = 7, RULE_cmdInstruction = 8, RULE_labelInstruction = 9, + RULE_exposeInstruction = 10, RULE_envInstruction = 11, RULE_addInstruction = 12, + RULE_copyInstruction = 13, RULE_entrypointInstruction = 14, RULE_volumeInstruction = 15, + RULE_userInstruction = 16, RULE_workdirInstruction = 17, RULE_argInstruction = 18, + RULE_onbuildInstruction = 19, RULE_stopsignalInstruction = 20, RULE_healthcheckInstruction = 21, + RULE_healthcheckOptions = 22, RULE_healthcheckOption = 23, RULE_shellInstruction = 24, + RULE_maintainerInstruction = 25, RULE_flags = 26, RULE_flag = 27, RULE_execForm = 28, + RULE_shellForm = 29, RULE_shellFormText = 30, RULE_shellFormTextElement = 31, + RULE_heredoc = 32, RULE_heredocPreamble = 33, RULE_preambleElement = 34, + RULE_heredocBody = 35, RULE_heredocContent = 36, RULE_heredocEnd = 37, + RULE_jsonArray = 38, RULE_jsonArrayElements = 39, RULE_jsonString = 40, + RULE_imageName = 41, RULE_stageName = 42, RULE_labelPairs = 43, RULE_labelPair = 44, + RULE_labelKey = 45, RULE_labelValue = 46, RULE_labelOldValue = 47, RULE_labelOldValueElement = 48, + RULE_portList = 49, RULE_port = 50, RULE_envPairs = 51, RULE_envPair = 52, + RULE_envKey = 53, RULE_envValueEquals = 54, RULE_envValueSpace = 55, RULE_envTextEquals = 56, + RULE_envTextElementEquals = 57, RULE_sourceList = 58, RULE_sourcePath = 59, + RULE_destination = 60, RULE_destinationPath = 61, RULE_path = 62, RULE_pathList = 63, + RULE_volumePath = 64, RULE_userSpec = 65, RULE_argName = 66, RULE_argValue = 67, + RULE_signal = 68, RULE_text = 69, RULE_textElement = 70; + private static String[] makeRuleNames() { + return new String[] { + "dockerfile", "parserDirective", "globalArgs", "stage", "stageInstruction", + "instruction", "fromInstruction", "runInstruction", "cmdInstruction", + "labelInstruction", "exposeInstruction", "envInstruction", "addInstruction", + "copyInstruction", "entrypointInstruction", "volumeInstruction", "userInstruction", + "workdirInstruction", "argInstruction", "onbuildInstruction", "stopsignalInstruction", + "healthcheckInstruction", "healthcheckOptions", "healthcheckOption", + "shellInstruction", "maintainerInstruction", "flags", "flag", "execForm", + "shellForm", "shellFormText", "shellFormTextElement", "heredoc", "heredocPreamble", + "preambleElement", "heredocBody", "heredocContent", "heredocEnd", "jsonArray", + "jsonArrayElements", "jsonString", "imageName", "stageName", "labelPairs", + "labelPair", "labelKey", "labelValue", "labelOldValue", "labelOldValueElement", + "portList", "port", "envPairs", "envPair", "envKey", "envValueEquals", + "envValueSpace", "envTextEquals", "envTextElementEquals", "sourceList", + "sourcePath", "destination", "destinationPath", "path", "pathList", "volumePath", + "userSpec", "argName", "argValue", "signal", "text", "textElement" + }; + } + public static final String[] ruleNames = makeRuleNames(); + + private static String[] makeLiteralNames() { + return new String[] { + null, null, null, "'FROM'", "'RUN'", "'CMD'", "'NONE'", "'LABEL'", "'EXPOSE'", + "'ENV'", "'ADD'", "'COPY'", "'ENTRYPOINT'", "'VOLUME'", "'USER'", "'WORKDIR'", + "'ARG'", "'ONBUILD'", "'STOPSIGNAL'", "'HEALTHCHECK'", "'SHELL'", "'MAINTAINER'", + "'AS'", null, null, "'['", "']'", "','", "'='", null, "'--'", null, null, + null, null, null, null, null, null, null, null, null, null, null, null, + "'\\n'" + }; + } + private static final String[] _LITERAL_NAMES = makeLiteralNames(); + private static String[] makeSymbolicNames() { + return new String[] { + null, "PARSER_DIRECTIVE", "COMMENT", "FROM", "RUN", "CMD", "NONE", "LABEL", + "EXPOSE", "ENV", "ADD", "COPY", "ENTRYPOINT", "VOLUME", "USER", "WORKDIR", + "ARG", "ONBUILD", "STOPSIGNAL", "HEALTHCHECK", "SHELL", "MAINTAINER", + "AS", "HEREDOC_START", "LINE_CONTINUATION", "LBRACKET", "RBRACKET", "COMMA", + "EQUALS", "FLAG", "DASH_DASH", "DOUBLE_QUOTED_STRING", "SINGLE_QUOTED_STRING", + "ENV_VAR", "SPECIAL_VAR", "COMMAND_SUBST", "BACKTICK_SUBST", "UNQUOTED_TEXT", + "WS", "NEWLINE", "HP_LINE_CONTINUATION", "HP_WS", "HP_COMMENT", "HP_LINE_COMMENT", + "HEREDOC_CONTENT", "H_NEWLINE" + }; + } + private static final String[] _SYMBOLIC_NAMES = makeSymbolicNames(); + public static final Vocabulary VOCABULARY = new VocabularyImpl(_LITERAL_NAMES, _SYMBOLIC_NAMES); + + /** + * @deprecated Use {@link #VOCABULARY} instead. + */ + @Deprecated + public static final String[] tokenNames; + static { + tokenNames = new String[_SYMBOLIC_NAMES.length]; + for (int i = 0; i < tokenNames.length; i++) { + tokenNames[i] = VOCABULARY.getLiteralName(i); + if (tokenNames[i] == null) { + tokenNames[i] = VOCABULARY.getSymbolicName(i); + } + + if (tokenNames[i] == null) { + tokenNames[i] = ""; + } + } + } + + @Override + @Deprecated + public String[] getTokenNames() { + return tokenNames; + } + + @Override + + public Vocabulary getVocabulary() { + return VOCABULARY; + } + + @Override + public String getGrammarFileName() { return "DockerParser.g4"; } + + @Override + public String[] getRuleNames() { return ruleNames; } + + @Override + public String getSerializedATN() { return _serializedATN; } + + @Override + public ATN getATN() { return _ATN; } + + public DockerParser(TokenStream input) { + super(input); + _interp = new ParserATNSimulator(this,_ATN,_decisionToDFA,_sharedContextCache); + } + + @SuppressWarnings("CheckReturnValue") + public static class DockerfileContext extends ParserRuleContext { + public GlobalArgsContext globalArgs() { + return getRuleContext(GlobalArgsContext.class,0); + } + public TerminalNode EOF() { return getToken(DockerParser.EOF, 0); } + public List parserDirective() { + return getRuleContexts(ParserDirectiveContext.class); + } + public ParserDirectiveContext parserDirective(int i) { + return getRuleContext(ParserDirectiveContext.class,i); + } + public List stage() { + return getRuleContexts(StageContext.class); + } + public StageContext stage(int i) { + return getRuleContext(StageContext.class,i); + } + public DockerfileContext(ParserRuleContext parent, int invokingState) { + super(parent, invokingState); + } + @Override public int getRuleIndex() { return RULE_dockerfile; } + @Override + public void enterRule(ParseTreeListener listener) { + if ( listener instanceof DockerParserListener ) ((DockerParserListener)listener).enterDockerfile(this); + } + @Override + public void exitRule(ParseTreeListener listener) { + if ( listener instanceof DockerParserListener ) ((DockerParserListener)listener).exitDockerfile(this); + } + @Override + public T accept(ParseTreeVisitor visitor) { + if ( visitor instanceof DockerParserVisitor ) return ((DockerParserVisitor)visitor).visitDockerfile(this); + else return visitor.visitChildren(this); + } + } + + public final DockerfileContext dockerfile() throws RecognitionException { + DockerfileContext _localctx = new DockerfileContext(_ctx, getState()); + enterRule(_localctx, 0, RULE_dockerfile); + int _la; + try { + enterOuterAlt(_localctx, 1); + { + setState(145); + _errHandler.sync(this); + _la = _input.LA(1); + while (_la==PARSER_DIRECTIVE) { + { + { + setState(142); + parserDirective(); + } + } + setState(147); + _errHandler.sync(this); + _la = _input.LA(1); + } + setState(148); + globalArgs(); + setState(150); + _errHandler.sync(this); + _la = _input.LA(1); + do { + { + { + setState(149); + stage(); + } + } + setState(152); + _errHandler.sync(this); + _la = _input.LA(1); + } while ( _la==FROM ); + setState(154); + match(EOF); + } + } + catch (RecognitionException re) { + _localctx.exception = re; + _errHandler.reportError(this, re); + _errHandler.recover(this, re); + } + finally { + exitRule(); + } + return _localctx; + } + + @SuppressWarnings("CheckReturnValue") + public static class ParserDirectiveContext extends ParserRuleContext { + public TerminalNode PARSER_DIRECTIVE() { return getToken(DockerParser.PARSER_DIRECTIVE, 0); } + public ParserDirectiveContext(ParserRuleContext parent, int invokingState) { + super(parent, invokingState); + } + @Override public int getRuleIndex() { return RULE_parserDirective; } + @Override + public void enterRule(ParseTreeListener listener) { + if ( listener instanceof DockerParserListener ) ((DockerParserListener)listener).enterParserDirective(this); + } + @Override + public void exitRule(ParseTreeListener listener) { + if ( listener instanceof DockerParserListener ) ((DockerParserListener)listener).exitParserDirective(this); + } + @Override + public T accept(ParseTreeVisitor visitor) { + if ( visitor instanceof DockerParserVisitor ) return ((DockerParserVisitor)visitor).visitParserDirective(this); + else return visitor.visitChildren(this); + } + } + + public final ParserDirectiveContext parserDirective() throws RecognitionException { + ParserDirectiveContext _localctx = new ParserDirectiveContext(_ctx, getState()); + enterRule(_localctx, 2, RULE_parserDirective); + try { + enterOuterAlt(_localctx, 1); + { + setState(156); + match(PARSER_DIRECTIVE); + } + } + catch (RecognitionException re) { + _localctx.exception = re; + _errHandler.reportError(this, re); + _errHandler.recover(this, re); + } + finally { + exitRule(); + } + return _localctx; + } + + @SuppressWarnings("CheckReturnValue") + public static class GlobalArgsContext extends ParserRuleContext { + public List argInstruction() { + return getRuleContexts(ArgInstructionContext.class); + } + public ArgInstructionContext argInstruction(int i) { + return getRuleContext(ArgInstructionContext.class,i); + } + public GlobalArgsContext(ParserRuleContext parent, int invokingState) { + super(parent, invokingState); + } + @Override public int getRuleIndex() { return RULE_globalArgs; } + @Override + public void enterRule(ParseTreeListener listener) { + if ( listener instanceof DockerParserListener ) ((DockerParserListener)listener).enterGlobalArgs(this); + } + @Override + public void exitRule(ParseTreeListener listener) { + if ( listener instanceof DockerParserListener ) ((DockerParserListener)listener).exitGlobalArgs(this); + } + @Override + public T accept(ParseTreeVisitor visitor) { + if ( visitor instanceof DockerParserVisitor ) return ((DockerParserVisitor)visitor).visitGlobalArgs(this); + else return visitor.visitChildren(this); + } + } + + public final GlobalArgsContext globalArgs() throws RecognitionException { + GlobalArgsContext _localctx = new GlobalArgsContext(_ctx, getState()); + enterRule(_localctx, 4, RULE_globalArgs); + int _la; + try { + enterOuterAlt(_localctx, 1); + { + setState(161); + _errHandler.sync(this); + _la = _input.LA(1); + while (_la==ARG) { + { + { + setState(158); + argInstruction(); + } + } + setState(163); + _errHandler.sync(this); + _la = _input.LA(1); + } + } + } + catch (RecognitionException re) { + _localctx.exception = re; + _errHandler.reportError(this, re); + _errHandler.recover(this, re); + } + finally { + exitRule(); + } + return _localctx; + } + + @SuppressWarnings("CheckReturnValue") + public static class StageContext extends ParserRuleContext { + public FromInstructionContext fromInstruction() { + return getRuleContext(FromInstructionContext.class,0); + } + public List stageInstruction() { + return getRuleContexts(StageInstructionContext.class); + } + public StageInstructionContext stageInstruction(int i) { + return getRuleContext(StageInstructionContext.class,i); + } + public StageContext(ParserRuleContext parent, int invokingState) { + super(parent, invokingState); + } + @Override public int getRuleIndex() { return RULE_stage; } + @Override + public void enterRule(ParseTreeListener listener) { + if ( listener instanceof DockerParserListener ) ((DockerParserListener)listener).enterStage(this); + } + @Override + public void exitRule(ParseTreeListener listener) { + if ( listener instanceof DockerParserListener ) ((DockerParserListener)listener).exitStage(this); + } + @Override + public T accept(ParseTreeVisitor visitor) { + if ( visitor instanceof DockerParserVisitor ) return ((DockerParserVisitor)visitor).visitStage(this); + else return visitor.visitChildren(this); + } + } + + public final StageContext stage() throws RecognitionException { + StageContext _localctx = new StageContext(_ctx, getState()); + enterRule(_localctx, 6, RULE_stage); + int _la; + try { + enterOuterAlt(_localctx, 1); + { + setState(164); + fromInstruction(); + setState(168); + _errHandler.sync(this); + _la = _input.LA(1); + while ((((_la) & ~0x3f) == 0 && ((1L << _la) & 4194224L) != 0)) { + { + { + setState(165); + stageInstruction(); + } + } + setState(170); + _errHandler.sync(this); + _la = _input.LA(1); + } + } + } + catch (RecognitionException re) { + _localctx.exception = re; + _errHandler.reportError(this, re); + _errHandler.recover(this, re); + } + finally { + exitRule(); + } + return _localctx; + } + + @SuppressWarnings("CheckReturnValue") + public static class StageInstructionContext extends ParserRuleContext { + public RunInstructionContext runInstruction() { + return getRuleContext(RunInstructionContext.class,0); + } + public CmdInstructionContext cmdInstruction() { + return getRuleContext(CmdInstructionContext.class,0); + } + public LabelInstructionContext labelInstruction() { + return getRuleContext(LabelInstructionContext.class,0); + } + public ExposeInstructionContext exposeInstruction() { + return getRuleContext(ExposeInstructionContext.class,0); + } + public EnvInstructionContext envInstruction() { + return getRuleContext(EnvInstructionContext.class,0); + } + public AddInstructionContext addInstruction() { + return getRuleContext(AddInstructionContext.class,0); + } + public CopyInstructionContext copyInstruction() { + return getRuleContext(CopyInstructionContext.class,0); + } + public EntrypointInstructionContext entrypointInstruction() { + return getRuleContext(EntrypointInstructionContext.class,0); + } + public VolumeInstructionContext volumeInstruction() { + return getRuleContext(VolumeInstructionContext.class,0); + } + public UserInstructionContext userInstruction() { + return getRuleContext(UserInstructionContext.class,0); + } + public WorkdirInstructionContext workdirInstruction() { + return getRuleContext(WorkdirInstructionContext.class,0); + } + public ArgInstructionContext argInstruction() { + return getRuleContext(ArgInstructionContext.class,0); + } + public OnbuildInstructionContext onbuildInstruction() { + return getRuleContext(OnbuildInstructionContext.class,0); + } + public StopsignalInstructionContext stopsignalInstruction() { + return getRuleContext(StopsignalInstructionContext.class,0); + } + public HealthcheckInstructionContext healthcheckInstruction() { + return getRuleContext(HealthcheckInstructionContext.class,0); + } + public ShellInstructionContext shellInstruction() { + return getRuleContext(ShellInstructionContext.class,0); + } + public MaintainerInstructionContext maintainerInstruction() { + return getRuleContext(MaintainerInstructionContext.class,0); + } + public StageInstructionContext(ParserRuleContext parent, int invokingState) { + super(parent, invokingState); + } + @Override public int getRuleIndex() { return RULE_stageInstruction; } + @Override + public void enterRule(ParseTreeListener listener) { + if ( listener instanceof DockerParserListener ) ((DockerParserListener)listener).enterStageInstruction(this); + } + @Override + public void exitRule(ParseTreeListener listener) { + if ( listener instanceof DockerParserListener ) ((DockerParserListener)listener).exitStageInstruction(this); + } + @Override + public T accept(ParseTreeVisitor visitor) { + if ( visitor instanceof DockerParserVisitor ) return ((DockerParserVisitor)visitor).visitStageInstruction(this); + else return visitor.visitChildren(this); + } + } + + public final StageInstructionContext stageInstruction() throws RecognitionException { + StageInstructionContext _localctx = new StageInstructionContext(_ctx, getState()); + enterRule(_localctx, 8, RULE_stageInstruction); + try { + setState(188); + _errHandler.sync(this); + switch (_input.LA(1)) { + case RUN: + enterOuterAlt(_localctx, 1); + { + setState(171); + runInstruction(); + } + break; + case CMD: + enterOuterAlt(_localctx, 2); + { + setState(172); + cmdInstruction(); + } + break; + case LABEL: + enterOuterAlt(_localctx, 3); + { + setState(173); + labelInstruction(); + } + break; + case EXPOSE: + enterOuterAlt(_localctx, 4); + { + setState(174); + exposeInstruction(); + } + break; + case ENV: + enterOuterAlt(_localctx, 5); + { + setState(175); + envInstruction(); + } + break; + case ADD: + enterOuterAlt(_localctx, 6); + { + setState(176); + addInstruction(); + } + break; + case COPY: + enterOuterAlt(_localctx, 7); + { + setState(177); + copyInstruction(); + } + break; + case ENTRYPOINT: + enterOuterAlt(_localctx, 8); + { + setState(178); + entrypointInstruction(); + } + break; + case VOLUME: + enterOuterAlt(_localctx, 9); + { + setState(179); + volumeInstruction(); + } + break; + case USER: + enterOuterAlt(_localctx, 10); + { + setState(180); + userInstruction(); + } + break; + case WORKDIR: + enterOuterAlt(_localctx, 11); + { + setState(181); + workdirInstruction(); + } + break; + case ARG: + enterOuterAlt(_localctx, 12); + { + setState(182); + argInstruction(); + } + break; + case ONBUILD: + enterOuterAlt(_localctx, 13); + { + setState(183); + onbuildInstruction(); + } + break; + case STOPSIGNAL: + enterOuterAlt(_localctx, 14); + { + setState(184); + stopsignalInstruction(); + } + break; + case HEALTHCHECK: + enterOuterAlt(_localctx, 15); + { + setState(185); + healthcheckInstruction(); + } + break; + case SHELL: + enterOuterAlt(_localctx, 16); + { + setState(186); + shellInstruction(); + } + break; + case MAINTAINER: + enterOuterAlt(_localctx, 17); + { + setState(187); + maintainerInstruction(); + } + break; + default: + throw new NoViableAltException(this); + } + } + catch (RecognitionException re) { + _localctx.exception = re; + _errHandler.reportError(this, re); + _errHandler.recover(this, re); + } + finally { + exitRule(); + } + return _localctx; + } + + @SuppressWarnings("CheckReturnValue") + public static class InstructionContext extends ParserRuleContext { + public FromInstructionContext fromInstruction() { + return getRuleContext(FromInstructionContext.class,0); + } + public StageInstructionContext stageInstruction() { + return getRuleContext(StageInstructionContext.class,0); + } + public InstructionContext(ParserRuleContext parent, int invokingState) { + super(parent, invokingState); + } + @Override public int getRuleIndex() { return RULE_instruction; } + @Override + public void enterRule(ParseTreeListener listener) { + if ( listener instanceof DockerParserListener ) ((DockerParserListener)listener).enterInstruction(this); + } + @Override + public void exitRule(ParseTreeListener listener) { + if ( listener instanceof DockerParserListener ) ((DockerParserListener)listener).exitInstruction(this); + } + @Override + public T accept(ParseTreeVisitor visitor) { + if ( visitor instanceof DockerParserVisitor ) return ((DockerParserVisitor)visitor).visitInstruction(this); + else return visitor.visitChildren(this); + } + } + + public final InstructionContext instruction() throws RecognitionException { + InstructionContext _localctx = new InstructionContext(_ctx, getState()); + enterRule(_localctx, 10, RULE_instruction); + try { + setState(192); + _errHandler.sync(this); + switch (_input.LA(1)) { + case FROM: + enterOuterAlt(_localctx, 1); + { + setState(190); + fromInstruction(); + } + break; + case RUN: + case CMD: + case LABEL: + case EXPOSE: + case ENV: + case ADD: + case COPY: + case ENTRYPOINT: + case VOLUME: + case USER: + case WORKDIR: + case ARG: + case ONBUILD: + case STOPSIGNAL: + case HEALTHCHECK: + case SHELL: + case MAINTAINER: + enterOuterAlt(_localctx, 2); + { + setState(191); + stageInstruction(); + } + break; + default: + throw new NoViableAltException(this); + } + } + catch (RecognitionException re) { + _localctx.exception = re; + _errHandler.reportError(this, re); + _errHandler.recover(this, re); + } + finally { + exitRule(); + } + return _localctx; + } + + @SuppressWarnings("CheckReturnValue") + public static class FromInstructionContext extends ParserRuleContext { + public TerminalNode FROM() { return getToken(DockerParser.FROM, 0); } + public ImageNameContext imageName() { + return getRuleContext(ImageNameContext.class,0); + } + public FlagsContext flags() { + return getRuleContext(FlagsContext.class,0); + } + public TerminalNode AS() { return getToken(DockerParser.AS, 0); } + public StageNameContext stageName() { + return getRuleContext(StageNameContext.class,0); + } + public FromInstructionContext(ParserRuleContext parent, int invokingState) { + super(parent, invokingState); + } + @Override public int getRuleIndex() { return RULE_fromInstruction; } + @Override + public void enterRule(ParseTreeListener listener) { + if ( listener instanceof DockerParserListener ) ((DockerParserListener)listener).enterFromInstruction(this); + } + @Override + public void exitRule(ParseTreeListener listener) { + if ( listener instanceof DockerParserListener ) ((DockerParserListener)listener).exitFromInstruction(this); + } + @Override + public T accept(ParseTreeVisitor visitor) { + if ( visitor instanceof DockerParserVisitor ) return ((DockerParserVisitor)visitor).visitFromInstruction(this); + else return visitor.visitChildren(this); + } + } + + public final FromInstructionContext fromInstruction() throws RecognitionException { + FromInstructionContext _localctx = new FromInstructionContext(_ctx, getState()); + enterRule(_localctx, 12, RULE_fromInstruction); + int _la; + try { + enterOuterAlt(_localctx, 1); + { + setState(194); + match(FROM); + setState(196); + _errHandler.sync(this); + switch ( getInterpreter().adaptivePredict(_input,6,_ctx) ) { + case 1: + { + setState(195); + flags(); + } + break; + } + setState(198); + imageName(); + setState(201); + _errHandler.sync(this); + _la = _input.LA(1); + if (_la==AS) { + { + setState(199); + match(AS); + setState(200); + stageName(); + } + } + + } + } + catch (RecognitionException re) { + _localctx.exception = re; + _errHandler.reportError(this, re); + _errHandler.recover(this, re); + } + finally { + exitRule(); + } + return _localctx; + } + + @SuppressWarnings("CheckReturnValue") + public static class RunInstructionContext extends ParserRuleContext { + public TerminalNode RUN() { return getToken(DockerParser.RUN, 0); } + public ExecFormContext execForm() { + return getRuleContext(ExecFormContext.class,0); + } + public ShellFormContext shellForm() { + return getRuleContext(ShellFormContext.class,0); + } + public HeredocContext heredoc() { + return getRuleContext(HeredocContext.class,0); + } + public FlagsContext flags() { + return getRuleContext(FlagsContext.class,0); + } + public RunInstructionContext(ParserRuleContext parent, int invokingState) { + super(parent, invokingState); + } + @Override public int getRuleIndex() { return RULE_runInstruction; } + @Override + public void enterRule(ParseTreeListener listener) { + if ( listener instanceof DockerParserListener ) ((DockerParserListener)listener).enterRunInstruction(this); + } + @Override + public void exitRule(ParseTreeListener listener) { + if ( listener instanceof DockerParserListener ) ((DockerParserListener)listener).exitRunInstruction(this); + } + @Override + public T accept(ParseTreeVisitor visitor) { + if ( visitor instanceof DockerParserVisitor ) return ((DockerParserVisitor)visitor).visitRunInstruction(this); + else return visitor.visitChildren(this); + } + } + + public final RunInstructionContext runInstruction() throws RecognitionException { + RunInstructionContext _localctx = new RunInstructionContext(_ctx, getState()); + enterRule(_localctx, 14, RULE_runInstruction); + try { + enterOuterAlt(_localctx, 1); + { + setState(203); + match(RUN); + setState(205); + _errHandler.sync(this); + switch ( getInterpreter().adaptivePredict(_input,8,_ctx) ) { + case 1: + { + setState(204); + flags(); + } + break; + } + setState(210); + _errHandler.sync(this); + switch ( getInterpreter().adaptivePredict(_input,9,_ctx) ) { + case 1: + { + setState(207); + execForm(); + } + break; + case 2: + { + setState(208); + shellForm(); + } + break; + case 3: + { + setState(209); + heredoc(); + } + break; + } + } + } + catch (RecognitionException re) { + _localctx.exception = re; + _errHandler.reportError(this, re); + _errHandler.recover(this, re); + } + finally { + exitRule(); + } + return _localctx; + } + + @SuppressWarnings("CheckReturnValue") + public static class CmdInstructionContext extends ParserRuleContext { + public TerminalNode CMD() { return getToken(DockerParser.CMD, 0); } + public ExecFormContext execForm() { + return getRuleContext(ExecFormContext.class,0); + } + public ShellFormContext shellForm() { + return getRuleContext(ShellFormContext.class,0); + } + public CmdInstructionContext(ParserRuleContext parent, int invokingState) { + super(parent, invokingState); + } + @Override public int getRuleIndex() { return RULE_cmdInstruction; } + @Override + public void enterRule(ParseTreeListener listener) { + if ( listener instanceof DockerParserListener ) ((DockerParserListener)listener).enterCmdInstruction(this); + } + @Override + public void exitRule(ParseTreeListener listener) { + if ( listener instanceof DockerParserListener ) ((DockerParserListener)listener).exitCmdInstruction(this); + } + @Override + public T accept(ParseTreeVisitor visitor) { + if ( visitor instanceof DockerParserVisitor ) return ((DockerParserVisitor)visitor).visitCmdInstruction(this); + else return visitor.visitChildren(this); + } + } + + public final CmdInstructionContext cmdInstruction() throws RecognitionException { + CmdInstructionContext _localctx = new CmdInstructionContext(_ctx, getState()); + enterRule(_localctx, 16, RULE_cmdInstruction); + try { + enterOuterAlt(_localctx, 1); + { + setState(212); + match(CMD); + setState(215); + _errHandler.sync(this); + switch ( getInterpreter().adaptivePredict(_input,10,_ctx) ) { + case 1: + { + setState(213); + execForm(); + } + break; + case 2: + { + setState(214); + shellForm(); + } + break; + } + } + } + catch (RecognitionException re) { + _localctx.exception = re; + _errHandler.reportError(this, re); + _errHandler.recover(this, re); + } + finally { + exitRule(); + } + return _localctx; + } + + @SuppressWarnings("CheckReturnValue") + public static class LabelInstructionContext extends ParserRuleContext { + public TerminalNode LABEL() { return getToken(DockerParser.LABEL, 0); } + public LabelPairsContext labelPairs() { + return getRuleContext(LabelPairsContext.class,0); + } + public LabelInstructionContext(ParserRuleContext parent, int invokingState) { + super(parent, invokingState); + } + @Override public int getRuleIndex() { return RULE_labelInstruction; } + @Override + public void enterRule(ParseTreeListener listener) { + if ( listener instanceof DockerParserListener ) ((DockerParserListener)listener).enterLabelInstruction(this); + } + @Override + public void exitRule(ParseTreeListener listener) { + if ( listener instanceof DockerParserListener ) ((DockerParserListener)listener).exitLabelInstruction(this); + } + @Override + public T accept(ParseTreeVisitor visitor) { + if ( visitor instanceof DockerParserVisitor ) return ((DockerParserVisitor)visitor).visitLabelInstruction(this); + else return visitor.visitChildren(this); + } + } + + public final LabelInstructionContext labelInstruction() throws RecognitionException { + LabelInstructionContext _localctx = new LabelInstructionContext(_ctx, getState()); + enterRule(_localctx, 18, RULE_labelInstruction); + try { + enterOuterAlt(_localctx, 1); + { + setState(217); + match(LABEL); + setState(218); + labelPairs(); + } + } + catch (RecognitionException re) { + _localctx.exception = re; + _errHandler.reportError(this, re); + _errHandler.recover(this, re); + } + finally { + exitRule(); + } + return _localctx; + } + + @SuppressWarnings("CheckReturnValue") + public static class ExposeInstructionContext extends ParserRuleContext { + public TerminalNode EXPOSE() { return getToken(DockerParser.EXPOSE, 0); } + public PortListContext portList() { + return getRuleContext(PortListContext.class,0); + } + public ExposeInstructionContext(ParserRuleContext parent, int invokingState) { + super(parent, invokingState); + } + @Override public int getRuleIndex() { return RULE_exposeInstruction; } + @Override + public void enterRule(ParseTreeListener listener) { + if ( listener instanceof DockerParserListener ) ((DockerParserListener)listener).enterExposeInstruction(this); + } + @Override + public void exitRule(ParseTreeListener listener) { + if ( listener instanceof DockerParserListener ) ((DockerParserListener)listener).exitExposeInstruction(this); + } + @Override + public T accept(ParseTreeVisitor visitor) { + if ( visitor instanceof DockerParserVisitor ) return ((DockerParserVisitor)visitor).visitExposeInstruction(this); + else return visitor.visitChildren(this); + } + } + + public final ExposeInstructionContext exposeInstruction() throws RecognitionException { + ExposeInstructionContext _localctx = new ExposeInstructionContext(_ctx, getState()); + enterRule(_localctx, 20, RULE_exposeInstruction); + try { + enterOuterAlt(_localctx, 1); + { + setState(220); + match(EXPOSE); + setState(221); + portList(); + } + } + catch (RecognitionException re) { + _localctx.exception = re; + _errHandler.reportError(this, re); + _errHandler.recover(this, re); + } + finally { + exitRule(); + } + return _localctx; + } + + @SuppressWarnings("CheckReturnValue") + public static class EnvInstructionContext extends ParserRuleContext { + public TerminalNode ENV() { return getToken(DockerParser.ENV, 0); } + public EnvPairsContext envPairs() { + return getRuleContext(EnvPairsContext.class,0); + } + public EnvInstructionContext(ParserRuleContext parent, int invokingState) { + super(parent, invokingState); + } + @Override public int getRuleIndex() { return RULE_envInstruction; } + @Override + public void enterRule(ParseTreeListener listener) { + if ( listener instanceof DockerParserListener ) ((DockerParserListener)listener).enterEnvInstruction(this); + } + @Override + public void exitRule(ParseTreeListener listener) { + if ( listener instanceof DockerParserListener ) ((DockerParserListener)listener).exitEnvInstruction(this); + } + @Override + public T accept(ParseTreeVisitor visitor) { + if ( visitor instanceof DockerParserVisitor ) return ((DockerParserVisitor)visitor).visitEnvInstruction(this); + else return visitor.visitChildren(this); + } + } + + public final EnvInstructionContext envInstruction() throws RecognitionException { + EnvInstructionContext _localctx = new EnvInstructionContext(_ctx, getState()); + enterRule(_localctx, 22, RULE_envInstruction); + try { + enterOuterAlt(_localctx, 1); + { + setState(223); + match(ENV); + setState(224); + envPairs(); + } + } + catch (RecognitionException re) { + _localctx.exception = re; + _errHandler.reportError(this, re); + _errHandler.recover(this, re); + } + finally { + exitRule(); + } + return _localctx; + } + + @SuppressWarnings("CheckReturnValue") + public static class AddInstructionContext extends ParserRuleContext { + public TerminalNode ADD() { return getToken(DockerParser.ADD, 0); } + public HeredocContext heredoc() { + return getRuleContext(HeredocContext.class,0); + } + public JsonArrayContext jsonArray() { + return getRuleContext(JsonArrayContext.class,0); + } + public SourceListContext sourceList() { + return getRuleContext(SourceListContext.class,0); + } + public DestinationContext destination() { + return getRuleContext(DestinationContext.class,0); + } + public FlagsContext flags() { + return getRuleContext(FlagsContext.class,0); + } + public AddInstructionContext(ParserRuleContext parent, int invokingState) { + super(parent, invokingState); + } + @Override public int getRuleIndex() { return RULE_addInstruction; } + @Override + public void enterRule(ParseTreeListener listener) { + if ( listener instanceof DockerParserListener ) ((DockerParserListener)listener).enterAddInstruction(this); + } + @Override + public void exitRule(ParseTreeListener listener) { + if ( listener instanceof DockerParserListener ) ((DockerParserListener)listener).exitAddInstruction(this); + } + @Override + public T accept(ParseTreeVisitor visitor) { + if ( visitor instanceof DockerParserVisitor ) return ((DockerParserVisitor)visitor).visitAddInstruction(this); + else return visitor.visitChildren(this); + } + } + + public final AddInstructionContext addInstruction() throws RecognitionException { + AddInstructionContext _localctx = new AddInstructionContext(_ctx, getState()); + enterRule(_localctx, 24, RULE_addInstruction); + int _la; + try { + enterOuterAlt(_localctx, 1); + { + setState(226); + match(ADD); + setState(228); + _errHandler.sync(this); + _la = _input.LA(1); + if (_la==FLAG) { + { + setState(227); + flags(); + } + } + + setState(235); + _errHandler.sync(this); + switch (_input.LA(1)) { + case HEREDOC_START: + { + setState(230); + heredoc(); + } + break; + case LBRACKET: + { + setState(231); + jsonArray(); + } + break; + case DOUBLE_QUOTED_STRING: + case SINGLE_QUOTED_STRING: + case ENV_VAR: + case SPECIAL_VAR: + case COMMAND_SUBST: + case BACKTICK_SUBST: + case UNQUOTED_TEXT: + { + setState(232); + sourceList(); + setState(233); + destination(); + } + break; + default: + throw new NoViableAltException(this); + } + } + } + catch (RecognitionException re) { + _localctx.exception = re; + _errHandler.reportError(this, re); + _errHandler.recover(this, re); + } + finally { + exitRule(); + } + return _localctx; + } + + @SuppressWarnings("CheckReturnValue") + public static class CopyInstructionContext extends ParserRuleContext { + public TerminalNode COPY() { return getToken(DockerParser.COPY, 0); } + public HeredocContext heredoc() { + return getRuleContext(HeredocContext.class,0); + } + public JsonArrayContext jsonArray() { + return getRuleContext(JsonArrayContext.class,0); + } + public SourceListContext sourceList() { + return getRuleContext(SourceListContext.class,0); + } + public DestinationContext destination() { + return getRuleContext(DestinationContext.class,0); + } + public FlagsContext flags() { + return getRuleContext(FlagsContext.class,0); + } + public CopyInstructionContext(ParserRuleContext parent, int invokingState) { + super(parent, invokingState); + } + @Override public int getRuleIndex() { return RULE_copyInstruction; } + @Override + public void enterRule(ParseTreeListener listener) { + if ( listener instanceof DockerParserListener ) ((DockerParserListener)listener).enterCopyInstruction(this); + } + @Override + public void exitRule(ParseTreeListener listener) { + if ( listener instanceof DockerParserListener ) ((DockerParserListener)listener).exitCopyInstruction(this); + } + @Override + public T accept(ParseTreeVisitor visitor) { + if ( visitor instanceof DockerParserVisitor ) return ((DockerParserVisitor)visitor).visitCopyInstruction(this); + else return visitor.visitChildren(this); + } + } + + public final CopyInstructionContext copyInstruction() throws RecognitionException { + CopyInstructionContext _localctx = new CopyInstructionContext(_ctx, getState()); + enterRule(_localctx, 26, RULE_copyInstruction); + int _la; + try { + enterOuterAlt(_localctx, 1); + { + setState(237); + match(COPY); + setState(239); + _errHandler.sync(this); + _la = _input.LA(1); + if (_la==FLAG) { + { + setState(238); + flags(); + } + } + + setState(246); + _errHandler.sync(this); + switch (_input.LA(1)) { + case HEREDOC_START: + { + setState(241); + heredoc(); + } + break; + case LBRACKET: + { + setState(242); + jsonArray(); + } + break; + case DOUBLE_QUOTED_STRING: + case SINGLE_QUOTED_STRING: + case ENV_VAR: + case SPECIAL_VAR: + case COMMAND_SUBST: + case BACKTICK_SUBST: + case UNQUOTED_TEXT: + { + setState(243); + sourceList(); + setState(244); + destination(); + } + break; + default: + throw new NoViableAltException(this); + } + } + } + catch (RecognitionException re) { + _localctx.exception = re; + _errHandler.reportError(this, re); + _errHandler.recover(this, re); + } + finally { + exitRule(); + } + return _localctx; + } + + @SuppressWarnings("CheckReturnValue") + public static class EntrypointInstructionContext extends ParserRuleContext { + public TerminalNode ENTRYPOINT() { return getToken(DockerParser.ENTRYPOINT, 0); } + public ExecFormContext execForm() { + return getRuleContext(ExecFormContext.class,0); + } + public ShellFormContext shellForm() { + return getRuleContext(ShellFormContext.class,0); + } + public EntrypointInstructionContext(ParserRuleContext parent, int invokingState) { + super(parent, invokingState); + } + @Override public int getRuleIndex() { return RULE_entrypointInstruction; } + @Override + public void enterRule(ParseTreeListener listener) { + if ( listener instanceof DockerParserListener ) ((DockerParserListener)listener).enterEntrypointInstruction(this); + } + @Override + public void exitRule(ParseTreeListener listener) { + if ( listener instanceof DockerParserListener ) ((DockerParserListener)listener).exitEntrypointInstruction(this); + } + @Override + public T accept(ParseTreeVisitor visitor) { + if ( visitor instanceof DockerParserVisitor ) return ((DockerParserVisitor)visitor).visitEntrypointInstruction(this); + else return visitor.visitChildren(this); + } + } + + public final EntrypointInstructionContext entrypointInstruction() throws RecognitionException { + EntrypointInstructionContext _localctx = new EntrypointInstructionContext(_ctx, getState()); + enterRule(_localctx, 28, RULE_entrypointInstruction); + try { + enterOuterAlt(_localctx, 1); + { + setState(248); + match(ENTRYPOINT); + setState(251); + _errHandler.sync(this); + switch ( getInterpreter().adaptivePredict(_input,15,_ctx) ) { + case 1: + { + setState(249); + execForm(); + } + break; + case 2: + { + setState(250); + shellForm(); + } + break; + } + } + } + catch (RecognitionException re) { + _localctx.exception = re; + _errHandler.reportError(this, re); + _errHandler.recover(this, re); + } + finally { + exitRule(); + } + return _localctx; + } + + @SuppressWarnings("CheckReturnValue") + public static class VolumeInstructionContext extends ParserRuleContext { + public TerminalNode VOLUME() { return getToken(DockerParser.VOLUME, 0); } + public JsonArrayContext jsonArray() { + return getRuleContext(JsonArrayContext.class,0); + } + public PathListContext pathList() { + return getRuleContext(PathListContext.class,0); + } + public VolumeInstructionContext(ParserRuleContext parent, int invokingState) { + super(parent, invokingState); + } + @Override public int getRuleIndex() { return RULE_volumeInstruction; } + @Override + public void enterRule(ParseTreeListener listener) { + if ( listener instanceof DockerParserListener ) ((DockerParserListener)listener).enterVolumeInstruction(this); + } + @Override + public void exitRule(ParseTreeListener listener) { + if ( listener instanceof DockerParserListener ) ((DockerParserListener)listener).exitVolumeInstruction(this); + } + @Override + public T accept(ParseTreeVisitor visitor) { + if ( visitor instanceof DockerParserVisitor ) return ((DockerParserVisitor)visitor).visitVolumeInstruction(this); + else return visitor.visitChildren(this); + } + } + + public final VolumeInstructionContext volumeInstruction() throws RecognitionException { + VolumeInstructionContext _localctx = new VolumeInstructionContext(_ctx, getState()); + enterRule(_localctx, 30, RULE_volumeInstruction); + try { + enterOuterAlt(_localctx, 1); + { + setState(253); + match(VOLUME); + setState(256); + _errHandler.sync(this); + switch (_input.LA(1)) { + case LBRACKET: + { + setState(254); + jsonArray(); + } + break; + case DOUBLE_QUOTED_STRING: + case SINGLE_QUOTED_STRING: + case ENV_VAR: + case SPECIAL_VAR: + case COMMAND_SUBST: + case BACKTICK_SUBST: + case UNQUOTED_TEXT: + { + setState(255); + pathList(); + } + break; + default: + throw new NoViableAltException(this); + } + } + } + catch (RecognitionException re) { + _localctx.exception = re; + _errHandler.reportError(this, re); + _errHandler.recover(this, re); + } + finally { + exitRule(); + } + return _localctx; + } + + @SuppressWarnings("CheckReturnValue") + public static class UserInstructionContext extends ParserRuleContext { + public TerminalNode USER() { return getToken(DockerParser.USER, 0); } + public UserSpecContext userSpec() { + return getRuleContext(UserSpecContext.class,0); + } + public UserInstructionContext(ParserRuleContext parent, int invokingState) { + super(parent, invokingState); + } + @Override public int getRuleIndex() { return RULE_userInstruction; } + @Override + public void enterRule(ParseTreeListener listener) { + if ( listener instanceof DockerParserListener ) ((DockerParserListener)listener).enterUserInstruction(this); + } + @Override + public void exitRule(ParseTreeListener listener) { + if ( listener instanceof DockerParserListener ) ((DockerParserListener)listener).exitUserInstruction(this); + } + @Override + public T accept(ParseTreeVisitor visitor) { + if ( visitor instanceof DockerParserVisitor ) return ((DockerParserVisitor)visitor).visitUserInstruction(this); + else return visitor.visitChildren(this); + } + } + + public final UserInstructionContext userInstruction() throws RecognitionException { + UserInstructionContext _localctx = new UserInstructionContext(_ctx, getState()); + enterRule(_localctx, 32, RULE_userInstruction); + try { + enterOuterAlt(_localctx, 1); + { + setState(258); + match(USER); + setState(259); + userSpec(); + } + } + catch (RecognitionException re) { + _localctx.exception = re; + _errHandler.reportError(this, re); + _errHandler.recover(this, re); + } + finally { + exitRule(); + } + return _localctx; + } + + @SuppressWarnings("CheckReturnValue") + public static class WorkdirInstructionContext extends ParserRuleContext { + public TerminalNode WORKDIR() { return getToken(DockerParser.WORKDIR, 0); } + public PathContext path() { + return getRuleContext(PathContext.class,0); + } + public WorkdirInstructionContext(ParserRuleContext parent, int invokingState) { + super(parent, invokingState); + } + @Override public int getRuleIndex() { return RULE_workdirInstruction; } + @Override + public void enterRule(ParseTreeListener listener) { + if ( listener instanceof DockerParserListener ) ((DockerParserListener)listener).enterWorkdirInstruction(this); + } + @Override + public void exitRule(ParseTreeListener listener) { + if ( listener instanceof DockerParserListener ) ((DockerParserListener)listener).exitWorkdirInstruction(this); + } + @Override + public T accept(ParseTreeVisitor visitor) { + if ( visitor instanceof DockerParserVisitor ) return ((DockerParserVisitor)visitor).visitWorkdirInstruction(this); + else return visitor.visitChildren(this); + } + } + + public final WorkdirInstructionContext workdirInstruction() throws RecognitionException { + WorkdirInstructionContext _localctx = new WorkdirInstructionContext(_ctx, getState()); + enterRule(_localctx, 34, RULE_workdirInstruction); + try { + enterOuterAlt(_localctx, 1); + { + setState(261); + match(WORKDIR); + setState(262); + path(); + } + } + catch (RecognitionException re) { + _localctx.exception = re; + _errHandler.reportError(this, re); + _errHandler.recover(this, re); + } + finally { + exitRule(); + } + return _localctx; + } + + @SuppressWarnings("CheckReturnValue") + public static class ArgInstructionContext extends ParserRuleContext { + public TerminalNode ARG() { return getToken(DockerParser.ARG, 0); } + public ArgNameContext argName() { + return getRuleContext(ArgNameContext.class,0); + } + public TerminalNode EQUALS() { return getToken(DockerParser.EQUALS, 0); } + public ArgValueContext argValue() { + return getRuleContext(ArgValueContext.class,0); + } + public ArgInstructionContext(ParserRuleContext parent, int invokingState) { + super(parent, invokingState); + } + @Override public int getRuleIndex() { return RULE_argInstruction; } + @Override + public void enterRule(ParseTreeListener listener) { + if ( listener instanceof DockerParserListener ) ((DockerParserListener)listener).enterArgInstruction(this); + } + @Override + public void exitRule(ParseTreeListener listener) { + if ( listener instanceof DockerParserListener ) ((DockerParserListener)listener).exitArgInstruction(this); + } + @Override + public T accept(ParseTreeVisitor visitor) { + if ( visitor instanceof DockerParserVisitor ) return ((DockerParserVisitor)visitor).visitArgInstruction(this); + else return visitor.visitChildren(this); + } + } + + public final ArgInstructionContext argInstruction() throws RecognitionException { + ArgInstructionContext _localctx = new ArgInstructionContext(_ctx, getState()); + enterRule(_localctx, 36, RULE_argInstruction); + int _la; + try { + enterOuterAlt(_localctx, 1); + { + setState(264); + match(ARG); + setState(265); + argName(); + setState(268); + _errHandler.sync(this); + _la = _input.LA(1); + if (_la==EQUALS) { + { + setState(266); + match(EQUALS); + setState(267); + argValue(); + } + } + + } + } + catch (RecognitionException re) { + _localctx.exception = re; + _errHandler.reportError(this, re); + _errHandler.recover(this, re); + } + finally { + exitRule(); + } + return _localctx; + } + + @SuppressWarnings("CheckReturnValue") + public static class OnbuildInstructionContext extends ParserRuleContext { + public TerminalNode ONBUILD() { return getToken(DockerParser.ONBUILD, 0); } + public InstructionContext instruction() { + return getRuleContext(InstructionContext.class,0); + } + public OnbuildInstructionContext(ParserRuleContext parent, int invokingState) { + super(parent, invokingState); + } + @Override public int getRuleIndex() { return RULE_onbuildInstruction; } + @Override + public void enterRule(ParseTreeListener listener) { + if ( listener instanceof DockerParserListener ) ((DockerParserListener)listener).enterOnbuildInstruction(this); + } + @Override + public void exitRule(ParseTreeListener listener) { + if ( listener instanceof DockerParserListener ) ((DockerParserListener)listener).exitOnbuildInstruction(this); + } + @Override + public T accept(ParseTreeVisitor visitor) { + if ( visitor instanceof DockerParserVisitor ) return ((DockerParserVisitor)visitor).visitOnbuildInstruction(this); + else return visitor.visitChildren(this); + } + } + + public final OnbuildInstructionContext onbuildInstruction() throws RecognitionException { + OnbuildInstructionContext _localctx = new OnbuildInstructionContext(_ctx, getState()); + enterRule(_localctx, 38, RULE_onbuildInstruction); + try { + enterOuterAlt(_localctx, 1); + { + setState(270); + match(ONBUILD); + setState(271); + instruction(); + } + } + catch (RecognitionException re) { + _localctx.exception = re; + _errHandler.reportError(this, re); + _errHandler.recover(this, re); + } + finally { + exitRule(); + } + return _localctx; + } + + @SuppressWarnings("CheckReturnValue") + public static class StopsignalInstructionContext extends ParserRuleContext { + public TerminalNode STOPSIGNAL() { return getToken(DockerParser.STOPSIGNAL, 0); } + public SignalContext signal() { + return getRuleContext(SignalContext.class,0); + } + public StopsignalInstructionContext(ParserRuleContext parent, int invokingState) { + super(parent, invokingState); + } + @Override public int getRuleIndex() { return RULE_stopsignalInstruction; } + @Override + public void enterRule(ParseTreeListener listener) { + if ( listener instanceof DockerParserListener ) ((DockerParserListener)listener).enterStopsignalInstruction(this); + } + @Override + public void exitRule(ParseTreeListener listener) { + if ( listener instanceof DockerParserListener ) ((DockerParserListener)listener).exitStopsignalInstruction(this); + } + @Override + public T accept(ParseTreeVisitor visitor) { + if ( visitor instanceof DockerParserVisitor ) return ((DockerParserVisitor)visitor).visitStopsignalInstruction(this); + else return visitor.visitChildren(this); + } + } + + public final StopsignalInstructionContext stopsignalInstruction() throws RecognitionException { + StopsignalInstructionContext _localctx = new StopsignalInstructionContext(_ctx, getState()); + enterRule(_localctx, 40, RULE_stopsignalInstruction); + try { + enterOuterAlt(_localctx, 1); + { + setState(273); + match(STOPSIGNAL); + setState(274); + signal(); + } + } + catch (RecognitionException re) { + _localctx.exception = re; + _errHandler.reportError(this, re); + _errHandler.recover(this, re); + } + finally { + exitRule(); + } + return _localctx; + } + + @SuppressWarnings("CheckReturnValue") + public static class HealthcheckInstructionContext extends ParserRuleContext { + public TerminalNode HEALTHCHECK() { return getToken(DockerParser.HEALTHCHECK, 0); } + public TerminalNode NONE() { return getToken(DockerParser.NONE, 0); } + public TerminalNode CMD() { return getToken(DockerParser.CMD, 0); } + public ExecFormContext execForm() { + return getRuleContext(ExecFormContext.class,0); + } + public ShellFormContext shellForm() { + return getRuleContext(ShellFormContext.class,0); + } + public HealthcheckOptionsContext healthcheckOptions() { + return getRuleContext(HealthcheckOptionsContext.class,0); + } + public HealthcheckInstructionContext(ParserRuleContext parent, int invokingState) { + super(parent, invokingState); + } + @Override public int getRuleIndex() { return RULE_healthcheckInstruction; } + @Override + public void enterRule(ParseTreeListener listener) { + if ( listener instanceof DockerParserListener ) ((DockerParserListener)listener).enterHealthcheckInstruction(this); + } + @Override + public void exitRule(ParseTreeListener listener) { + if ( listener instanceof DockerParserListener ) ((DockerParserListener)listener).exitHealthcheckInstruction(this); + } + @Override + public T accept(ParseTreeVisitor visitor) { + if ( visitor instanceof DockerParserVisitor ) return ((DockerParserVisitor)visitor).visitHealthcheckInstruction(this); + else return visitor.visitChildren(this); + } + } + + public final HealthcheckInstructionContext healthcheckInstruction() throws RecognitionException { + HealthcheckInstructionContext _localctx = new HealthcheckInstructionContext(_ctx, getState()); + enterRule(_localctx, 42, RULE_healthcheckInstruction); + int _la; + try { + setState(287); + _errHandler.sync(this); + switch ( getInterpreter().adaptivePredict(_input,20,_ctx) ) { + case 1: + enterOuterAlt(_localctx, 1); + { + setState(276); + match(HEALTHCHECK); + setState(277); + match(NONE); + } + break; + case 2: + enterOuterAlt(_localctx, 2); + { + setState(278); + match(HEALTHCHECK); + setState(280); + _errHandler.sync(this); + _la = _input.LA(1); + if (_la==FLAG) { + { + setState(279); + healthcheckOptions(); + } + } + + setState(282); + match(CMD); + setState(285); + _errHandler.sync(this); + switch ( getInterpreter().adaptivePredict(_input,19,_ctx) ) { + case 1: + { + setState(283); + execForm(); + } + break; + case 2: + { + setState(284); + shellForm(); + } + break; + } + } + break; + } + } + catch (RecognitionException re) { + _localctx.exception = re; + _errHandler.reportError(this, re); + _errHandler.recover(this, re); + } + finally { + exitRule(); + } + return _localctx; + } + + @SuppressWarnings("CheckReturnValue") + public static class HealthcheckOptionsContext extends ParserRuleContext { + public List healthcheckOption() { + return getRuleContexts(HealthcheckOptionContext.class); + } + public HealthcheckOptionContext healthcheckOption(int i) { + return getRuleContext(HealthcheckOptionContext.class,i); + } + public HealthcheckOptionsContext(ParserRuleContext parent, int invokingState) { + super(parent, invokingState); + } + @Override public int getRuleIndex() { return RULE_healthcheckOptions; } + @Override + public void enterRule(ParseTreeListener listener) { + if ( listener instanceof DockerParserListener ) ((DockerParserListener)listener).enterHealthcheckOptions(this); + } + @Override + public void exitRule(ParseTreeListener listener) { + if ( listener instanceof DockerParserListener ) ((DockerParserListener)listener).exitHealthcheckOptions(this); + } + @Override + public T accept(ParseTreeVisitor visitor) { + if ( visitor instanceof DockerParserVisitor ) return ((DockerParserVisitor)visitor).visitHealthcheckOptions(this); + else return visitor.visitChildren(this); + } + } + + public final HealthcheckOptionsContext healthcheckOptions() throws RecognitionException { + HealthcheckOptionsContext _localctx = new HealthcheckOptionsContext(_ctx, getState()); + enterRule(_localctx, 44, RULE_healthcheckOptions); + int _la; + try { + enterOuterAlt(_localctx, 1); + { + setState(290); + _errHandler.sync(this); + _la = _input.LA(1); + do { + { + { + setState(289); + healthcheckOption(); + } + } + setState(292); + _errHandler.sync(this); + _la = _input.LA(1); + } while ( _la==FLAG ); + } + } + catch (RecognitionException re) { + _localctx.exception = re; + _errHandler.reportError(this, re); + _errHandler.recover(this, re); + } + finally { + exitRule(); + } + return _localctx; + } + + @SuppressWarnings("CheckReturnValue") + public static class HealthcheckOptionContext extends ParserRuleContext { + public TerminalNode FLAG() { return getToken(DockerParser.FLAG, 0); } + public HealthcheckOptionContext(ParserRuleContext parent, int invokingState) { + super(parent, invokingState); + } + @Override public int getRuleIndex() { return RULE_healthcheckOption; } + @Override + public void enterRule(ParseTreeListener listener) { + if ( listener instanceof DockerParserListener ) ((DockerParserListener)listener).enterHealthcheckOption(this); + } + @Override + public void exitRule(ParseTreeListener listener) { + if ( listener instanceof DockerParserListener ) ((DockerParserListener)listener).exitHealthcheckOption(this); + } + @Override + public T accept(ParseTreeVisitor visitor) { + if ( visitor instanceof DockerParserVisitor ) return ((DockerParserVisitor)visitor).visitHealthcheckOption(this); + else return visitor.visitChildren(this); + } + } + + public final HealthcheckOptionContext healthcheckOption() throws RecognitionException { + HealthcheckOptionContext _localctx = new HealthcheckOptionContext(_ctx, getState()); + enterRule(_localctx, 46, RULE_healthcheckOption); + try { + enterOuterAlt(_localctx, 1); + { + setState(294); + match(FLAG); + } + } + catch (RecognitionException re) { + _localctx.exception = re; + _errHandler.reportError(this, re); + _errHandler.recover(this, re); + } + finally { + exitRule(); + } + return _localctx; + } + + @SuppressWarnings("CheckReturnValue") + public static class ShellInstructionContext extends ParserRuleContext { + public TerminalNode SHELL() { return getToken(DockerParser.SHELL, 0); } + public JsonArrayContext jsonArray() { + return getRuleContext(JsonArrayContext.class,0); + } + public ShellInstructionContext(ParserRuleContext parent, int invokingState) { + super(parent, invokingState); + } + @Override public int getRuleIndex() { return RULE_shellInstruction; } + @Override + public void enterRule(ParseTreeListener listener) { + if ( listener instanceof DockerParserListener ) ((DockerParserListener)listener).enterShellInstruction(this); + } + @Override + public void exitRule(ParseTreeListener listener) { + if ( listener instanceof DockerParserListener ) ((DockerParserListener)listener).exitShellInstruction(this); + } + @Override + public T accept(ParseTreeVisitor visitor) { + if ( visitor instanceof DockerParserVisitor ) return ((DockerParserVisitor)visitor).visitShellInstruction(this); + else return visitor.visitChildren(this); + } + } + + public final ShellInstructionContext shellInstruction() throws RecognitionException { + ShellInstructionContext _localctx = new ShellInstructionContext(_ctx, getState()); + enterRule(_localctx, 48, RULE_shellInstruction); + try { + enterOuterAlt(_localctx, 1); + { + setState(296); + match(SHELL); + setState(297); + jsonArray(); + } + } + catch (RecognitionException re) { + _localctx.exception = re; + _errHandler.reportError(this, re); + _errHandler.recover(this, re); + } + finally { + exitRule(); + } + return _localctx; + } + + @SuppressWarnings("CheckReturnValue") + public static class MaintainerInstructionContext extends ParserRuleContext { + public TerminalNode MAINTAINER() { return getToken(DockerParser.MAINTAINER, 0); } + public TextContext text() { + return getRuleContext(TextContext.class,0); + } + public MaintainerInstructionContext(ParserRuleContext parent, int invokingState) { + super(parent, invokingState); + } + @Override public int getRuleIndex() { return RULE_maintainerInstruction; } + @Override + public void enterRule(ParseTreeListener listener) { + if ( listener instanceof DockerParserListener ) ((DockerParserListener)listener).enterMaintainerInstruction(this); + } + @Override + public void exitRule(ParseTreeListener listener) { + if ( listener instanceof DockerParserListener ) ((DockerParserListener)listener).exitMaintainerInstruction(this); + } + @Override + public T accept(ParseTreeVisitor visitor) { + if ( visitor instanceof DockerParserVisitor ) return ((DockerParserVisitor)visitor).visitMaintainerInstruction(this); + else return visitor.visitChildren(this); + } + } + + public final MaintainerInstructionContext maintainerInstruction() throws RecognitionException { + MaintainerInstructionContext _localctx = new MaintainerInstructionContext(_ctx, getState()); + enterRule(_localctx, 50, RULE_maintainerInstruction); + try { + enterOuterAlt(_localctx, 1); + { + setState(299); + match(MAINTAINER); + setState(300); + text(); + } + } + catch (RecognitionException re) { + _localctx.exception = re; + _errHandler.reportError(this, re); + _errHandler.recover(this, re); + } + finally { + exitRule(); + } + return _localctx; + } + + @SuppressWarnings("CheckReturnValue") + public static class FlagsContext extends ParserRuleContext { + public List flag() { + return getRuleContexts(FlagContext.class); + } + public FlagContext flag(int i) { + return getRuleContext(FlagContext.class,i); + } + public FlagsContext(ParserRuleContext parent, int invokingState) { + super(parent, invokingState); + } + @Override public int getRuleIndex() { return RULE_flags; } + @Override + public void enterRule(ParseTreeListener listener) { + if ( listener instanceof DockerParserListener ) ((DockerParserListener)listener).enterFlags(this); + } + @Override + public void exitRule(ParseTreeListener listener) { + if ( listener instanceof DockerParserListener ) ((DockerParserListener)listener).exitFlags(this); + } + @Override + public T accept(ParseTreeVisitor visitor) { + if ( visitor instanceof DockerParserVisitor ) return ((DockerParserVisitor)visitor).visitFlags(this); + else return visitor.visitChildren(this); + } + } + + public final FlagsContext flags() throws RecognitionException { + FlagsContext _localctx = new FlagsContext(_ctx, getState()); + enterRule(_localctx, 52, RULE_flags); + try { + int _alt; + enterOuterAlt(_localctx, 1); + { + setState(303); + _errHandler.sync(this); + _alt = 1; + do { + switch (_alt) { + case 1: + { + { + setState(302); + flag(); + } + } + break; + default: + throw new NoViableAltException(this); + } + setState(305); + _errHandler.sync(this); + _alt = getInterpreter().adaptivePredict(_input,22,_ctx); + } while ( _alt!=2 && _alt!=org.antlr.v4.runtime.atn.ATN.INVALID_ALT_NUMBER ); + } + } + catch (RecognitionException re) { + _localctx.exception = re; + _errHandler.reportError(this, re); + _errHandler.recover(this, re); + } + finally { + exitRule(); + } + return _localctx; + } + + @SuppressWarnings("CheckReturnValue") + public static class FlagContext extends ParserRuleContext { + public TerminalNode FLAG() { return getToken(DockerParser.FLAG, 0); } + public FlagContext(ParserRuleContext parent, int invokingState) { + super(parent, invokingState); + } + @Override public int getRuleIndex() { return RULE_flag; } + @Override + public void enterRule(ParseTreeListener listener) { + if ( listener instanceof DockerParserListener ) ((DockerParserListener)listener).enterFlag(this); + } + @Override + public void exitRule(ParseTreeListener listener) { + if ( listener instanceof DockerParserListener ) ((DockerParserListener)listener).exitFlag(this); + } + @Override + public T accept(ParseTreeVisitor visitor) { + if ( visitor instanceof DockerParserVisitor ) return ((DockerParserVisitor)visitor).visitFlag(this); + else return visitor.visitChildren(this); + } + } + + public final FlagContext flag() throws RecognitionException { + FlagContext _localctx = new FlagContext(_ctx, getState()); + enterRule(_localctx, 54, RULE_flag); + try { + enterOuterAlt(_localctx, 1); + { + setState(307); + match(FLAG); + } + } + catch (RecognitionException re) { + _localctx.exception = re; + _errHandler.reportError(this, re); + _errHandler.recover(this, re); + } + finally { + exitRule(); + } + return _localctx; + } + + @SuppressWarnings("CheckReturnValue") + public static class ExecFormContext extends ParserRuleContext { + public JsonArrayContext jsonArray() { + return getRuleContext(JsonArrayContext.class,0); + } + public ExecFormContext(ParserRuleContext parent, int invokingState) { + super(parent, invokingState); + } + @Override public int getRuleIndex() { return RULE_execForm; } + @Override + public void enterRule(ParseTreeListener listener) { + if ( listener instanceof DockerParserListener ) ((DockerParserListener)listener).enterExecForm(this); + } + @Override + public void exitRule(ParseTreeListener listener) { + if ( listener instanceof DockerParserListener ) ((DockerParserListener)listener).exitExecForm(this); + } + @Override + public T accept(ParseTreeVisitor visitor) { + if ( visitor instanceof DockerParserVisitor ) return ((DockerParserVisitor)visitor).visitExecForm(this); + else return visitor.visitChildren(this); + } + } + + public final ExecFormContext execForm() throws RecognitionException { + ExecFormContext _localctx = new ExecFormContext(_ctx, getState()); + enterRule(_localctx, 56, RULE_execForm); + try { + enterOuterAlt(_localctx, 1); + { + setState(309); + jsonArray(); + } + } + catch (RecognitionException re) { + _localctx.exception = re; + _errHandler.reportError(this, re); + _errHandler.recover(this, re); + } + finally { + exitRule(); + } + return _localctx; + } + + @SuppressWarnings("CheckReturnValue") + public static class ShellFormContext extends ParserRuleContext { + public ShellFormTextContext shellFormText() { + return getRuleContext(ShellFormTextContext.class,0); + } + public ShellFormContext(ParserRuleContext parent, int invokingState) { + super(parent, invokingState); + } + @Override public int getRuleIndex() { return RULE_shellForm; } + @Override + public void enterRule(ParseTreeListener listener) { + if ( listener instanceof DockerParserListener ) ((DockerParserListener)listener).enterShellForm(this); + } + @Override + public void exitRule(ParseTreeListener listener) { + if ( listener instanceof DockerParserListener ) ((DockerParserListener)listener).exitShellForm(this); + } + @Override + public T accept(ParseTreeVisitor visitor) { + if ( visitor instanceof DockerParserVisitor ) return ((DockerParserVisitor)visitor).visitShellForm(this); + else return visitor.visitChildren(this); + } + } + + public final ShellFormContext shellForm() throws RecognitionException { + ShellFormContext _localctx = new ShellFormContext(_ctx, getState()); + enterRule(_localctx, 58, RULE_shellForm); + try { + enterOuterAlt(_localctx, 1); + { + setState(311); + shellFormText(); + } + } + catch (RecognitionException re) { + _localctx.exception = re; + _errHandler.reportError(this, re); + _errHandler.recover(this, re); + } + finally { + exitRule(); + } + return _localctx; + } + + @SuppressWarnings("CheckReturnValue") + public static class ShellFormTextContext extends ParserRuleContext { + public List shellFormTextElement() { + return getRuleContexts(ShellFormTextElementContext.class); + } + public ShellFormTextElementContext shellFormTextElement(int i) { + return getRuleContext(ShellFormTextElementContext.class,i); + } + public ShellFormTextContext(ParserRuleContext parent, int invokingState) { + super(parent, invokingState); + } + @Override public int getRuleIndex() { return RULE_shellFormText; } + @Override + public void enterRule(ParseTreeListener listener) { + if ( listener instanceof DockerParserListener ) ((DockerParserListener)listener).enterShellFormText(this); + } + @Override + public void exitRule(ParseTreeListener listener) { + if ( listener instanceof DockerParserListener ) ((DockerParserListener)listener).exitShellFormText(this); + } + @Override + public T accept(ParseTreeVisitor visitor) { + if ( visitor instanceof DockerParserVisitor ) return ((DockerParserVisitor)visitor).visitShellFormText(this); + else return visitor.visitChildren(this); + } + } + + public final ShellFormTextContext shellFormText() throws RecognitionException { + ShellFormTextContext _localctx = new ShellFormTextContext(_ctx, getState()); + enterRule(_localctx, 60, RULE_shellFormText); + int _la; + try { + enterOuterAlt(_localctx, 1); + { + setState(314); + _errHandler.sync(this); + _la = _input.LA(1); + do { + { + { + setState(313); + shellFormTextElement(); + } + } + setState(316); + _errHandler.sync(this); + _la = _input.LA(1); + } while ( (((_la) & ~0x3f) == 0 && ((1L << _la) & 274844352512L) != 0) ); + } + } + catch (RecognitionException re) { + _localctx.exception = re; + _errHandler.reportError(this, re); + _errHandler.recover(this, re); + } + finally { + exitRule(); + } + return _localctx; + } + + @SuppressWarnings("CheckReturnValue") + public static class ShellFormTextElementContext extends ParserRuleContext { + public TerminalNode UNQUOTED_TEXT() { return getToken(DockerParser.UNQUOTED_TEXT, 0); } + public TerminalNode DOUBLE_QUOTED_STRING() { return getToken(DockerParser.DOUBLE_QUOTED_STRING, 0); } + public TerminalNode SINGLE_QUOTED_STRING() { return getToken(DockerParser.SINGLE_QUOTED_STRING, 0); } + public TerminalNode ENV_VAR() { return getToken(DockerParser.ENV_VAR, 0); } + public TerminalNode COMMAND_SUBST() { return getToken(DockerParser.COMMAND_SUBST, 0); } + public TerminalNode BACKTICK_SUBST() { return getToken(DockerParser.BACKTICK_SUBST, 0); } + public TerminalNode SPECIAL_VAR() { return getToken(DockerParser.SPECIAL_VAR, 0); } + public TerminalNode EQUALS() { return getToken(DockerParser.EQUALS, 0); } + public TerminalNode FLAG() { return getToken(DockerParser.FLAG, 0); } + public TerminalNode DASH_DASH() { return getToken(DockerParser.DASH_DASH, 0); } + public TerminalNode LBRACKET() { return getToken(DockerParser.LBRACKET, 0); } + public TerminalNode RBRACKET() { return getToken(DockerParser.RBRACKET, 0); } + public TerminalNode COMMA() { return getToken(DockerParser.COMMA, 0); } + public ShellFormTextElementContext(ParserRuleContext parent, int invokingState) { + super(parent, invokingState); + } + @Override public int getRuleIndex() { return RULE_shellFormTextElement; } + @Override + public void enterRule(ParseTreeListener listener) { + if ( listener instanceof DockerParserListener ) ((DockerParserListener)listener).enterShellFormTextElement(this); + } + @Override + public void exitRule(ParseTreeListener listener) { + if ( listener instanceof DockerParserListener ) ((DockerParserListener)listener).exitShellFormTextElement(this); + } + @Override + public T accept(ParseTreeVisitor visitor) { + if ( visitor instanceof DockerParserVisitor ) return ((DockerParserVisitor)visitor).visitShellFormTextElement(this); + else return visitor.visitChildren(this); + } + } + + public final ShellFormTextElementContext shellFormTextElement() throws RecognitionException { + ShellFormTextElementContext _localctx = new ShellFormTextElementContext(_ctx, getState()); + enterRule(_localctx, 62, RULE_shellFormTextElement); + int _la; + try { + enterOuterAlt(_localctx, 1); + { + setState(318); + _la = _input.LA(1); + if ( !((((_la) & ~0x3f) == 0 && ((1L << _la) & 274844352512L) != 0)) ) { + _errHandler.recoverInline(this); + } + else { + if ( _input.LA(1)==Token.EOF ) matchedEOF = true; + _errHandler.reportMatch(this); + consume(); + } + } + } + catch (RecognitionException re) { + _localctx.exception = re; + _errHandler.reportError(this, re); + _errHandler.recover(this, re); + } + finally { + exitRule(); + } + return _localctx; + } + + @SuppressWarnings("CheckReturnValue") + public static class HeredocContext extends ParserRuleContext { + public HeredocPreambleContext heredocPreamble() { + return getRuleContext(HeredocPreambleContext.class,0); + } + public TerminalNode NEWLINE() { return getToken(DockerParser.NEWLINE, 0); } + public List heredocBody() { + return getRuleContexts(HeredocBodyContext.class); + } + public HeredocBodyContext heredocBody(int i) { + return getRuleContext(HeredocBodyContext.class,i); + } + public HeredocContext(ParserRuleContext parent, int invokingState) { + super(parent, invokingState); + } + @Override public int getRuleIndex() { return RULE_heredoc; } + @Override + public void enterRule(ParseTreeListener listener) { + if ( listener instanceof DockerParserListener ) ((DockerParserListener)listener).enterHeredoc(this); + } + @Override + public void exitRule(ParseTreeListener listener) { + if ( listener instanceof DockerParserListener ) ((DockerParserListener)listener).exitHeredoc(this); + } + @Override + public T accept(ParseTreeVisitor visitor) { + if ( visitor instanceof DockerParserVisitor ) return ((DockerParserVisitor)visitor).visitHeredoc(this); + else return visitor.visitChildren(this); + } + } + + public final HeredocContext heredoc() throws RecognitionException { + HeredocContext _localctx = new HeredocContext(_ctx, getState()); + enterRule(_localctx, 64, RULE_heredoc); + int _la; + try { + enterOuterAlt(_localctx, 1); + { + setState(320); + heredocPreamble(); + setState(321); + match(NEWLINE); + setState(323); + _errHandler.sync(this); + _la = _input.LA(1); + do { + { + { + setState(322); + heredocBody(); + } + } + setState(325); + _errHandler.sync(this); + _la = _input.LA(1); + } while ( (((_la) & ~0x3f) == 0 && ((1L << _la) & 18279380811776L) != 0) ); + } + } + catch (RecognitionException re) { + _localctx.exception = re; + _errHandler.reportError(this, re); + _errHandler.recover(this, re); + } + finally { + exitRule(); + } + return _localctx; + } + + @SuppressWarnings("CheckReturnValue") + public static class HeredocPreambleContext extends ParserRuleContext { + public List HEREDOC_START() { return getTokens(DockerParser.HEREDOC_START); } + public TerminalNode HEREDOC_START(int i) { + return getToken(DockerParser.HEREDOC_START, i); + } + public List preambleElement() { + return getRuleContexts(PreambleElementContext.class); + } + public PreambleElementContext preambleElement(int i) { + return getRuleContext(PreambleElementContext.class,i); + } + public HeredocPreambleContext(ParserRuleContext parent, int invokingState) { + super(parent, invokingState); + } + @Override public int getRuleIndex() { return RULE_heredocPreamble; } + @Override + public void enterRule(ParseTreeListener listener) { + if ( listener instanceof DockerParserListener ) ((DockerParserListener)listener).enterHeredocPreamble(this); + } + @Override + public void exitRule(ParseTreeListener listener) { + if ( listener instanceof DockerParserListener ) ((DockerParserListener)listener).exitHeredocPreamble(this); + } + @Override + public T accept(ParseTreeVisitor visitor) { + if ( visitor instanceof DockerParserVisitor ) return ((DockerParserVisitor)visitor).visitHeredocPreamble(this); + else return visitor.visitChildren(this); + } + } + + public final HeredocPreambleContext heredocPreamble() throws RecognitionException { + HeredocPreambleContext _localctx = new HeredocPreambleContext(_ctx, getState()); + enterRule(_localctx, 66, RULE_heredocPreamble); + int _la; + try { + enterOuterAlt(_localctx, 1); + { + setState(327); + match(HEREDOC_START); + setState(331); + _errHandler.sync(this); + _la = _input.LA(1); + while ((((_la) & ~0x3f) == 0 && ((1L << _la) & 274844352512L) != 0)) { + { + { + setState(328); + preambleElement(); + } + } + setState(333); + _errHandler.sync(this); + _la = _input.LA(1); + } + setState(343); + _errHandler.sync(this); + _la = _input.LA(1); + while (_la==HEREDOC_START) { + { + { + setState(334); + match(HEREDOC_START); + setState(338); + _errHandler.sync(this); + _la = _input.LA(1); + while ((((_la) & ~0x3f) == 0 && ((1L << _la) & 274844352512L) != 0)) { + { + { + setState(335); + preambleElement(); + } + } + setState(340); + _errHandler.sync(this); + _la = _input.LA(1); + } + } + } + setState(345); + _errHandler.sync(this); + _la = _input.LA(1); + } + } + } + catch (RecognitionException re) { + _localctx.exception = re; + _errHandler.reportError(this, re); + _errHandler.recover(this, re); + } + finally { + exitRule(); + } + return _localctx; + } + + @SuppressWarnings("CheckReturnValue") + public static class PreambleElementContext extends ParserRuleContext { + public TerminalNode UNQUOTED_TEXT() { return getToken(DockerParser.UNQUOTED_TEXT, 0); } + public TerminalNode DOUBLE_QUOTED_STRING() { return getToken(DockerParser.DOUBLE_QUOTED_STRING, 0); } + public TerminalNode SINGLE_QUOTED_STRING() { return getToken(DockerParser.SINGLE_QUOTED_STRING, 0); } + public TerminalNode ENV_VAR() { return getToken(DockerParser.ENV_VAR, 0); } + public TerminalNode COMMAND_SUBST() { return getToken(DockerParser.COMMAND_SUBST, 0); } + public TerminalNode BACKTICK_SUBST() { return getToken(DockerParser.BACKTICK_SUBST, 0); } + public TerminalNode SPECIAL_VAR() { return getToken(DockerParser.SPECIAL_VAR, 0); } + public TerminalNode EQUALS() { return getToken(DockerParser.EQUALS, 0); } + public TerminalNode FLAG() { return getToken(DockerParser.FLAG, 0); } + public TerminalNode DASH_DASH() { return getToken(DockerParser.DASH_DASH, 0); } + public TerminalNode LBRACKET() { return getToken(DockerParser.LBRACKET, 0); } + public TerminalNode RBRACKET() { return getToken(DockerParser.RBRACKET, 0); } + public TerminalNode COMMA() { return getToken(DockerParser.COMMA, 0); } + public PreambleElementContext(ParserRuleContext parent, int invokingState) { + super(parent, invokingState); + } + @Override public int getRuleIndex() { return RULE_preambleElement; } + @Override + public void enterRule(ParseTreeListener listener) { + if ( listener instanceof DockerParserListener ) ((DockerParserListener)listener).enterPreambleElement(this); + } + @Override + public void exitRule(ParseTreeListener listener) { + if ( listener instanceof DockerParserListener ) ((DockerParserListener)listener).exitPreambleElement(this); + } + @Override + public T accept(ParseTreeVisitor visitor) { + if ( visitor instanceof DockerParserVisitor ) return ((DockerParserVisitor)visitor).visitPreambleElement(this); + else return visitor.visitChildren(this); + } + } + + public final PreambleElementContext preambleElement() throws RecognitionException { + PreambleElementContext _localctx = new PreambleElementContext(_ctx, getState()); + enterRule(_localctx, 68, RULE_preambleElement); + int _la; + try { + enterOuterAlt(_localctx, 1); + { + setState(346); + _la = _input.LA(1); + if ( !((((_la) & ~0x3f) == 0 && ((1L << _la) & 274844352512L) != 0)) ) { + _errHandler.recoverInline(this); + } + else { + if ( _input.LA(1)==Token.EOF ) matchedEOF = true; + _errHandler.reportMatch(this); + consume(); + } + } + } + catch (RecognitionException re) { + _localctx.exception = re; + _errHandler.reportError(this, re); + _errHandler.recover(this, re); + } + finally { + exitRule(); + } + return _localctx; + } + + @SuppressWarnings("CheckReturnValue") + public static class HeredocBodyContext extends ParserRuleContext { + public HeredocContentContext heredocContent() { + return getRuleContext(HeredocContentContext.class,0); + } + public HeredocEndContext heredocEnd() { + return getRuleContext(HeredocEndContext.class,0); + } + public HeredocBodyContext(ParserRuleContext parent, int invokingState) { + super(parent, invokingState); + } + @Override public int getRuleIndex() { return RULE_heredocBody; } + @Override + public void enterRule(ParseTreeListener listener) { + if ( listener instanceof DockerParserListener ) ((DockerParserListener)listener).enterHeredocBody(this); + } + @Override + public void exitRule(ParseTreeListener listener) { + if ( listener instanceof DockerParserListener ) ((DockerParserListener)listener).exitHeredocBody(this); + } + @Override + public T accept(ParseTreeVisitor visitor) { + if ( visitor instanceof DockerParserVisitor ) return ((DockerParserVisitor)visitor).visitHeredocBody(this); + else return visitor.visitChildren(this); + } + } + + public final HeredocBodyContext heredocBody() throws RecognitionException { + HeredocBodyContext _localctx = new HeredocBodyContext(_ctx, getState()); + enterRule(_localctx, 70, RULE_heredocBody); + try { + enterOuterAlt(_localctx, 1); + { + setState(348); + heredocContent(); + setState(349); + heredocEnd(); + } + } + catch (RecognitionException re) { + _localctx.exception = re; + _errHandler.reportError(this, re); + _errHandler.recover(this, re); + } + finally { + exitRule(); + } + return _localctx; + } + + @SuppressWarnings("CheckReturnValue") + public static class HeredocContentContext extends ParserRuleContext { + public List NEWLINE() { return getTokens(DockerParser.NEWLINE); } + public TerminalNode NEWLINE(int i) { + return getToken(DockerParser.NEWLINE, i); + } + public List HEREDOC_CONTENT() { return getTokens(DockerParser.HEREDOC_CONTENT); } + public TerminalNode HEREDOC_CONTENT(int i) { + return getToken(DockerParser.HEREDOC_CONTENT, i); + } + public HeredocContentContext(ParserRuleContext parent, int invokingState) { + super(parent, invokingState); + } + @Override public int getRuleIndex() { return RULE_heredocContent; } + @Override + public void enterRule(ParseTreeListener listener) { + if ( listener instanceof DockerParserListener ) ((DockerParserListener)listener).enterHeredocContent(this); + } + @Override + public void exitRule(ParseTreeListener listener) { + if ( listener instanceof DockerParserListener ) ((DockerParserListener)listener).exitHeredocContent(this); + } + @Override + public T accept(ParseTreeVisitor visitor) { + if ( visitor instanceof DockerParserVisitor ) return ((DockerParserVisitor)visitor).visitHeredocContent(this); + else return visitor.visitChildren(this); + } + } + + public final HeredocContentContext heredocContent() throws RecognitionException { + HeredocContentContext _localctx = new HeredocContentContext(_ctx, getState()); + enterRule(_localctx, 72, RULE_heredocContent); + int _la; + try { + enterOuterAlt(_localctx, 1); + { + setState(354); + _errHandler.sync(this); + _la = _input.LA(1); + while (_la==NEWLINE || _la==HEREDOC_CONTENT) { + { + { + setState(351); + _la = _input.LA(1); + if ( !(_la==NEWLINE || _la==HEREDOC_CONTENT) ) { + _errHandler.recoverInline(this); + } + else { + if ( _input.LA(1)==Token.EOF ) matchedEOF = true; + _errHandler.reportMatch(this); + consume(); + } + } + } + setState(356); + _errHandler.sync(this); + _la = _input.LA(1); + } + } + } + catch (RecognitionException re) { + _localctx.exception = re; + _errHandler.reportError(this, re); + _errHandler.recover(this, re); + } + finally { + exitRule(); + } + return _localctx; + } + + @SuppressWarnings("CheckReturnValue") + public static class HeredocEndContext extends ParserRuleContext { + public TerminalNode UNQUOTED_TEXT() { return getToken(DockerParser.UNQUOTED_TEXT, 0); } + public HeredocEndContext(ParserRuleContext parent, int invokingState) { + super(parent, invokingState); + } + @Override public int getRuleIndex() { return RULE_heredocEnd; } + @Override + public void enterRule(ParseTreeListener listener) { + if ( listener instanceof DockerParserListener ) ((DockerParserListener)listener).enterHeredocEnd(this); + } + @Override + public void exitRule(ParseTreeListener listener) { + if ( listener instanceof DockerParserListener ) ((DockerParserListener)listener).exitHeredocEnd(this); + } + @Override + public T accept(ParseTreeVisitor visitor) { + if ( visitor instanceof DockerParserVisitor ) return ((DockerParserVisitor)visitor).visitHeredocEnd(this); + else return visitor.visitChildren(this); + } + } + + public final HeredocEndContext heredocEnd() throws RecognitionException { + HeredocEndContext _localctx = new HeredocEndContext(_ctx, getState()); + enterRule(_localctx, 74, RULE_heredocEnd); + try { + enterOuterAlt(_localctx, 1); + { + setState(357); + match(UNQUOTED_TEXT); + } + } + catch (RecognitionException re) { + _localctx.exception = re; + _errHandler.reportError(this, re); + _errHandler.recover(this, re); + } + finally { + exitRule(); + } + return _localctx; + } + + @SuppressWarnings("CheckReturnValue") + public static class JsonArrayContext extends ParserRuleContext { + public TerminalNode LBRACKET() { return getToken(DockerParser.LBRACKET, 0); } + public TerminalNode RBRACKET() { return getToken(DockerParser.RBRACKET, 0); } + public JsonArrayElementsContext jsonArrayElements() { + return getRuleContext(JsonArrayElementsContext.class,0); + } + public JsonArrayContext(ParserRuleContext parent, int invokingState) { + super(parent, invokingState); + } + @Override public int getRuleIndex() { return RULE_jsonArray; } + @Override + public void enterRule(ParseTreeListener listener) { + if ( listener instanceof DockerParserListener ) ((DockerParserListener)listener).enterJsonArray(this); + } + @Override + public void exitRule(ParseTreeListener listener) { + if ( listener instanceof DockerParserListener ) ((DockerParserListener)listener).exitJsonArray(this); + } + @Override + public T accept(ParseTreeVisitor visitor) { + if ( visitor instanceof DockerParserVisitor ) return ((DockerParserVisitor)visitor).visitJsonArray(this); + else return visitor.visitChildren(this); + } + } + + public final JsonArrayContext jsonArray() throws RecognitionException { + JsonArrayContext _localctx = new JsonArrayContext(_ctx, getState()); + enterRule(_localctx, 76, RULE_jsonArray); + int _la; + try { + enterOuterAlt(_localctx, 1); + { + setState(359); + match(LBRACKET); + setState(361); + _errHandler.sync(this); + _la = _input.LA(1); + if (_la==DOUBLE_QUOTED_STRING) { + { + setState(360); + jsonArrayElements(); + } + } + + setState(363); + match(RBRACKET); + } + } + catch (RecognitionException re) { + _localctx.exception = re; + _errHandler.reportError(this, re); + _errHandler.recover(this, re); + } + finally { + exitRule(); + } + return _localctx; + } + + @SuppressWarnings("CheckReturnValue") + public static class JsonArrayElementsContext extends ParserRuleContext { + public List jsonString() { + return getRuleContexts(JsonStringContext.class); + } + public JsonStringContext jsonString(int i) { + return getRuleContext(JsonStringContext.class,i); + } + public List COMMA() { return getTokens(DockerParser.COMMA); } + public TerminalNode COMMA(int i) { + return getToken(DockerParser.COMMA, i); + } + public JsonArrayElementsContext(ParserRuleContext parent, int invokingState) { + super(parent, invokingState); + } + @Override public int getRuleIndex() { return RULE_jsonArrayElements; } + @Override + public void enterRule(ParseTreeListener listener) { + if ( listener instanceof DockerParserListener ) ((DockerParserListener)listener).enterJsonArrayElements(this); + } + @Override + public void exitRule(ParseTreeListener listener) { + if ( listener instanceof DockerParserListener ) ((DockerParserListener)listener).exitJsonArrayElements(this); + } + @Override + public T accept(ParseTreeVisitor visitor) { + if ( visitor instanceof DockerParserVisitor ) return ((DockerParserVisitor)visitor).visitJsonArrayElements(this); + else return visitor.visitChildren(this); + } + } + + public final JsonArrayElementsContext jsonArrayElements() throws RecognitionException { + JsonArrayElementsContext _localctx = new JsonArrayElementsContext(_ctx, getState()); + enterRule(_localctx, 78, RULE_jsonArrayElements); + int _la; + try { + enterOuterAlt(_localctx, 1); + { + setState(365); + jsonString(); + setState(370); + _errHandler.sync(this); + _la = _input.LA(1); + while (_la==COMMA) { + { + { + setState(366); + match(COMMA); + setState(367); + jsonString(); + } + } + setState(372); + _errHandler.sync(this); + _la = _input.LA(1); + } + } + } + catch (RecognitionException re) { + _localctx.exception = re; + _errHandler.reportError(this, re); + _errHandler.recover(this, re); + } + finally { + exitRule(); + } + return _localctx; + } + + @SuppressWarnings("CheckReturnValue") + public static class JsonStringContext extends ParserRuleContext { + public TerminalNode DOUBLE_QUOTED_STRING() { return getToken(DockerParser.DOUBLE_QUOTED_STRING, 0); } + public JsonStringContext(ParserRuleContext parent, int invokingState) { + super(parent, invokingState); + } + @Override public int getRuleIndex() { return RULE_jsonString; } + @Override + public void enterRule(ParseTreeListener listener) { + if ( listener instanceof DockerParserListener ) ((DockerParserListener)listener).enterJsonString(this); + } + @Override + public void exitRule(ParseTreeListener listener) { + if ( listener instanceof DockerParserListener ) ((DockerParserListener)listener).exitJsonString(this); + } + @Override + public T accept(ParseTreeVisitor visitor) { + if ( visitor instanceof DockerParserVisitor ) return ((DockerParserVisitor)visitor).visitJsonString(this); + else return visitor.visitChildren(this); + } + } + + public final JsonStringContext jsonString() throws RecognitionException { + JsonStringContext _localctx = new JsonStringContext(_ctx, getState()); + enterRule(_localctx, 80, RULE_jsonString); + try { + enterOuterAlt(_localctx, 1); + { + setState(373); + match(DOUBLE_QUOTED_STRING); + } + } + catch (RecognitionException re) { + _localctx.exception = re; + _errHandler.reportError(this, re); + _errHandler.recover(this, re); + } + finally { + exitRule(); + } + return _localctx; + } + + @SuppressWarnings("CheckReturnValue") + public static class ImageNameContext extends ParserRuleContext { + public TextContext text() { + return getRuleContext(TextContext.class,0); + } + public ImageNameContext(ParserRuleContext parent, int invokingState) { + super(parent, invokingState); + } + @Override public int getRuleIndex() { return RULE_imageName; } + @Override + public void enterRule(ParseTreeListener listener) { + if ( listener instanceof DockerParserListener ) ((DockerParserListener)listener).enterImageName(this); + } + @Override + public void exitRule(ParseTreeListener listener) { + if ( listener instanceof DockerParserListener ) ((DockerParserListener)listener).exitImageName(this); + } + @Override + public T accept(ParseTreeVisitor visitor) { + if ( visitor instanceof DockerParserVisitor ) return ((DockerParserVisitor)visitor).visitImageName(this); + else return visitor.visitChildren(this); + } + } + + public final ImageNameContext imageName() throws RecognitionException { + ImageNameContext _localctx = new ImageNameContext(_ctx, getState()); + enterRule(_localctx, 82, RULE_imageName); + try { + enterOuterAlt(_localctx, 1); + { + setState(375); + text(); + } + } + catch (RecognitionException re) { + _localctx.exception = re; + _errHandler.reportError(this, re); + _errHandler.recover(this, re); + } + finally { + exitRule(); + } + return _localctx; + } + + @SuppressWarnings("CheckReturnValue") + public static class StageNameContext extends ParserRuleContext { + public TerminalNode UNQUOTED_TEXT() { return getToken(DockerParser.UNQUOTED_TEXT, 0); } + public StageNameContext(ParserRuleContext parent, int invokingState) { + super(parent, invokingState); + } + @Override public int getRuleIndex() { return RULE_stageName; } + @Override + public void enterRule(ParseTreeListener listener) { + if ( listener instanceof DockerParserListener ) ((DockerParserListener)listener).enterStageName(this); + } + @Override + public void exitRule(ParseTreeListener listener) { + if ( listener instanceof DockerParserListener ) ((DockerParserListener)listener).exitStageName(this); + } + @Override + public T accept(ParseTreeVisitor visitor) { + if ( visitor instanceof DockerParserVisitor ) return ((DockerParserVisitor)visitor).visitStageName(this); + else return visitor.visitChildren(this); + } + } + + public final StageNameContext stageName() throws RecognitionException { + StageNameContext _localctx = new StageNameContext(_ctx, getState()); + enterRule(_localctx, 84, RULE_stageName); + try { + enterOuterAlt(_localctx, 1); + { + setState(377); + match(UNQUOTED_TEXT); + } + } + catch (RecognitionException re) { + _localctx.exception = re; + _errHandler.reportError(this, re); + _errHandler.recover(this, re); + } + finally { + exitRule(); + } + return _localctx; + } + + @SuppressWarnings("CheckReturnValue") + public static class LabelPairsContext extends ParserRuleContext { + public List labelPair() { + return getRuleContexts(LabelPairContext.class); + } + public LabelPairContext labelPair(int i) { + return getRuleContext(LabelPairContext.class,i); + } + public LabelPairsContext(ParserRuleContext parent, int invokingState) { + super(parent, invokingState); + } + @Override public int getRuleIndex() { return RULE_labelPairs; } + @Override + public void enterRule(ParseTreeListener listener) { + if ( listener instanceof DockerParserListener ) ((DockerParserListener)listener).enterLabelPairs(this); + } + @Override + public void exitRule(ParseTreeListener listener) { + if ( listener instanceof DockerParserListener ) ((DockerParserListener)listener).exitLabelPairs(this); + } + @Override + public T accept(ParseTreeVisitor visitor) { + if ( visitor instanceof DockerParserVisitor ) return ((DockerParserVisitor)visitor).visitLabelPairs(this); + else return visitor.visitChildren(this); + } + } + + public final LabelPairsContext labelPairs() throws RecognitionException { + LabelPairsContext _localctx = new LabelPairsContext(_ctx, getState()); + enterRule(_localctx, 86, RULE_labelPairs); + int _la; + try { + enterOuterAlt(_localctx, 1); + { + setState(380); + _errHandler.sync(this); + _la = _input.LA(1); + do { + { + { + setState(379); + labelPair(); + } + } + setState(382); + _errHandler.sync(this); + _la = _input.LA(1); + } while ( (((_la) & ~0x3f) == 0 && ((1L << _la) & 143881404416L) != 0) ); + } + } + catch (RecognitionException re) { + _localctx.exception = re; + _errHandler.reportError(this, re); + _errHandler.recover(this, re); + } + finally { + exitRule(); + } + return _localctx; + } + + @SuppressWarnings("CheckReturnValue") + public static class LabelPairContext extends ParserRuleContext { + public LabelKeyContext labelKey() { + return getRuleContext(LabelKeyContext.class,0); + } + public TerminalNode EQUALS() { return getToken(DockerParser.EQUALS, 0); } + public LabelValueContext labelValue() { + return getRuleContext(LabelValueContext.class,0); + } + public LabelOldValueContext labelOldValue() { + return getRuleContext(LabelOldValueContext.class,0); + } + public LabelPairContext(ParserRuleContext parent, int invokingState) { + super(parent, invokingState); + } + @Override public int getRuleIndex() { return RULE_labelPair; } + @Override + public void enterRule(ParseTreeListener listener) { + if ( listener instanceof DockerParserListener ) ((DockerParserListener)listener).enterLabelPair(this); + } + @Override + public void exitRule(ParseTreeListener listener) { + if ( listener instanceof DockerParserListener ) ((DockerParserListener)listener).exitLabelPair(this); + } + @Override + public T accept(ParseTreeVisitor visitor) { + if ( visitor instanceof DockerParserVisitor ) return ((DockerParserVisitor)visitor).visitLabelPair(this); + else return visitor.visitChildren(this); + } + } + + public final LabelPairContext labelPair() throws RecognitionException { + LabelPairContext _localctx = new LabelPairContext(_ctx, getState()); + enterRule(_localctx, 88, RULE_labelPair); + try { + setState(391); + _errHandler.sync(this); + switch ( getInterpreter().adaptivePredict(_input,32,_ctx) ) { + case 1: + enterOuterAlt(_localctx, 1); + { + setState(384); + labelKey(); + setState(385); + match(EQUALS); + setState(386); + labelValue(); + } + break; + case 2: + enterOuterAlt(_localctx, 2); + { + setState(388); + labelKey(); + setState(389); + labelOldValue(); + } + break; + } + } + catch (RecognitionException re) { + _localctx.exception = re; + _errHandler.reportError(this, re); + _errHandler.recover(this, re); + } + finally { + exitRule(); + } + return _localctx; + } + + @SuppressWarnings("CheckReturnValue") + public static class LabelKeyContext extends ParserRuleContext { + public TerminalNode UNQUOTED_TEXT() { return getToken(DockerParser.UNQUOTED_TEXT, 0); } + public TerminalNode DOUBLE_QUOTED_STRING() { return getToken(DockerParser.DOUBLE_QUOTED_STRING, 0); } + public TerminalNode SINGLE_QUOTED_STRING() { return getToken(DockerParser.SINGLE_QUOTED_STRING, 0); } + public LabelKeyContext(ParserRuleContext parent, int invokingState) { + super(parent, invokingState); + } + @Override public int getRuleIndex() { return RULE_labelKey; } + @Override + public void enterRule(ParseTreeListener listener) { + if ( listener instanceof DockerParserListener ) ((DockerParserListener)listener).enterLabelKey(this); + } + @Override + public void exitRule(ParseTreeListener listener) { + if ( listener instanceof DockerParserListener ) ((DockerParserListener)listener).exitLabelKey(this); + } + @Override + public T accept(ParseTreeVisitor visitor) { + if ( visitor instanceof DockerParserVisitor ) return ((DockerParserVisitor)visitor).visitLabelKey(this); + else return visitor.visitChildren(this); + } + } + + public final LabelKeyContext labelKey() throws RecognitionException { + LabelKeyContext _localctx = new LabelKeyContext(_ctx, getState()); + enterRule(_localctx, 90, RULE_labelKey); + int _la; + try { + enterOuterAlt(_localctx, 1); + { + setState(393); + _la = _input.LA(1); + if ( !((((_la) & ~0x3f) == 0 && ((1L << _la) & 143881404416L) != 0)) ) { + _errHandler.recoverInline(this); + } + else { + if ( _input.LA(1)==Token.EOF ) matchedEOF = true; + _errHandler.reportMatch(this); + consume(); + } + } + } + catch (RecognitionException re) { + _localctx.exception = re; + _errHandler.reportError(this, re); + _errHandler.recover(this, re); + } + finally { + exitRule(); + } + return _localctx; + } + + @SuppressWarnings("CheckReturnValue") + public static class LabelValueContext extends ParserRuleContext { + public TerminalNode UNQUOTED_TEXT() { return getToken(DockerParser.UNQUOTED_TEXT, 0); } + public TerminalNode DOUBLE_QUOTED_STRING() { return getToken(DockerParser.DOUBLE_QUOTED_STRING, 0); } + public TerminalNode SINGLE_QUOTED_STRING() { return getToken(DockerParser.SINGLE_QUOTED_STRING, 0); } + public LabelValueContext(ParserRuleContext parent, int invokingState) { + super(parent, invokingState); + } + @Override public int getRuleIndex() { return RULE_labelValue; } + @Override + public void enterRule(ParseTreeListener listener) { + if ( listener instanceof DockerParserListener ) ((DockerParserListener)listener).enterLabelValue(this); + } + @Override + public void exitRule(ParseTreeListener listener) { + if ( listener instanceof DockerParserListener ) ((DockerParserListener)listener).exitLabelValue(this); + } + @Override + public T accept(ParseTreeVisitor visitor) { + if ( visitor instanceof DockerParserVisitor ) return ((DockerParserVisitor)visitor).visitLabelValue(this); + else return visitor.visitChildren(this); + } + } + + public final LabelValueContext labelValue() throws RecognitionException { + LabelValueContext _localctx = new LabelValueContext(_ctx, getState()); + enterRule(_localctx, 92, RULE_labelValue); + int _la; + try { + enterOuterAlt(_localctx, 1); + { + setState(395); + _la = _input.LA(1); + if ( !((((_la) & ~0x3f) == 0 && ((1L << _la) & 143881404416L) != 0)) ) { + _errHandler.recoverInline(this); + } + else { + if ( _input.LA(1)==Token.EOF ) matchedEOF = true; + _errHandler.reportMatch(this); + consume(); + } + } + } + catch (RecognitionException re) { + _localctx.exception = re; + _errHandler.reportError(this, re); + _errHandler.recover(this, re); + } + finally { + exitRule(); + } + return _localctx; + } + + @SuppressWarnings("CheckReturnValue") + public static class LabelOldValueContext extends ParserRuleContext { + public List labelOldValueElement() { + return getRuleContexts(LabelOldValueElementContext.class); + } + public LabelOldValueElementContext labelOldValueElement(int i) { + return getRuleContext(LabelOldValueElementContext.class,i); + } + public LabelOldValueContext(ParserRuleContext parent, int invokingState) { + super(parent, invokingState); + } + @Override public int getRuleIndex() { return RULE_labelOldValue; } + @Override + public void enterRule(ParseTreeListener listener) { + if ( listener instanceof DockerParserListener ) ((DockerParserListener)listener).enterLabelOldValue(this); + } + @Override + public void exitRule(ParseTreeListener listener) { + if ( listener instanceof DockerParserListener ) ((DockerParserListener)listener).exitLabelOldValue(this); + } + @Override + public T accept(ParseTreeVisitor visitor) { + if ( visitor instanceof DockerParserVisitor ) return ((DockerParserVisitor)visitor).visitLabelOldValue(this); + else return visitor.visitChildren(this); + } + } + + public final LabelOldValueContext labelOldValue() throws RecognitionException { + LabelOldValueContext _localctx = new LabelOldValueContext(_ctx, getState()); + enterRule(_localctx, 94, RULE_labelOldValue); + try { + int _alt; + enterOuterAlt(_localctx, 1); + { + setState(398); + _errHandler.sync(this); + _alt = 1; + do { + switch (_alt) { + case 1: + { + { + setState(397); + labelOldValueElement(); + } + } + break; + default: + throw new NoViableAltException(this); + } + setState(400); + _errHandler.sync(this); + _alt = getInterpreter().adaptivePredict(_input,33,_ctx); + } while ( _alt!=2 && _alt!=org.antlr.v4.runtime.atn.ATN.INVALID_ALT_NUMBER ); + } + } + catch (RecognitionException re) { + _localctx.exception = re; + _errHandler.reportError(this, re); + _errHandler.recover(this, re); + } + finally { + exitRule(); + } + return _localctx; + } + + @SuppressWarnings("CheckReturnValue") + public static class LabelOldValueElementContext extends ParserRuleContext { + public TerminalNode UNQUOTED_TEXT() { return getToken(DockerParser.UNQUOTED_TEXT, 0); } + public TerminalNode DOUBLE_QUOTED_STRING() { return getToken(DockerParser.DOUBLE_QUOTED_STRING, 0); } + public TerminalNode SINGLE_QUOTED_STRING() { return getToken(DockerParser.SINGLE_QUOTED_STRING, 0); } + public TerminalNode ENV_VAR() { return getToken(DockerParser.ENV_VAR, 0); } + public TerminalNode COMMAND_SUBST() { return getToken(DockerParser.COMMAND_SUBST, 0); } + public TerminalNode BACKTICK_SUBST() { return getToken(DockerParser.BACKTICK_SUBST, 0); } + public TerminalNode SPECIAL_VAR() { return getToken(DockerParser.SPECIAL_VAR, 0); } + public TerminalNode EQUALS() { return getToken(DockerParser.EQUALS, 0); } + public TerminalNode FLAG() { return getToken(DockerParser.FLAG, 0); } + public TerminalNode DASH_DASH() { return getToken(DockerParser.DASH_DASH, 0); } + public TerminalNode LBRACKET() { return getToken(DockerParser.LBRACKET, 0); } + public TerminalNode RBRACKET() { return getToken(DockerParser.RBRACKET, 0); } + public TerminalNode COMMA() { return getToken(DockerParser.COMMA, 0); } + public LabelOldValueElementContext(ParserRuleContext parent, int invokingState) { + super(parent, invokingState); + } + @Override public int getRuleIndex() { return RULE_labelOldValueElement; } + @Override + public void enterRule(ParseTreeListener listener) { + if ( listener instanceof DockerParserListener ) ((DockerParserListener)listener).enterLabelOldValueElement(this); + } + @Override + public void exitRule(ParseTreeListener listener) { + if ( listener instanceof DockerParserListener ) ((DockerParserListener)listener).exitLabelOldValueElement(this); + } + @Override + public T accept(ParseTreeVisitor visitor) { + if ( visitor instanceof DockerParserVisitor ) return ((DockerParserVisitor)visitor).visitLabelOldValueElement(this); + else return visitor.visitChildren(this); + } + } + + public final LabelOldValueElementContext labelOldValueElement() throws RecognitionException { + LabelOldValueElementContext _localctx = new LabelOldValueElementContext(_ctx, getState()); + enterRule(_localctx, 96, RULE_labelOldValueElement); + int _la; + try { + enterOuterAlt(_localctx, 1); + { + setState(402); + _la = _input.LA(1); + if ( !((((_la) & ~0x3f) == 0 && ((1L << _la) & 274844352512L) != 0)) ) { + _errHandler.recoverInline(this); + } + else { + if ( _input.LA(1)==Token.EOF ) matchedEOF = true; + _errHandler.reportMatch(this); + consume(); + } + } + } + catch (RecognitionException re) { + _localctx.exception = re; + _errHandler.reportError(this, re); + _errHandler.recover(this, re); + } + finally { + exitRule(); + } + return _localctx; + } + + @SuppressWarnings("CheckReturnValue") + public static class PortListContext extends ParserRuleContext { + public List port() { + return getRuleContexts(PortContext.class); + } + public PortContext port(int i) { + return getRuleContext(PortContext.class,i); + } + public PortListContext(ParserRuleContext parent, int invokingState) { + super(parent, invokingState); + } + @Override public int getRuleIndex() { return RULE_portList; } + @Override + public void enterRule(ParseTreeListener listener) { + if ( listener instanceof DockerParserListener ) ((DockerParserListener)listener).enterPortList(this); + } + @Override + public void exitRule(ParseTreeListener listener) { + if ( listener instanceof DockerParserListener ) ((DockerParserListener)listener).exitPortList(this); + } + @Override + public T accept(ParseTreeVisitor visitor) { + if ( visitor instanceof DockerParserVisitor ) return ((DockerParserVisitor)visitor).visitPortList(this); + else return visitor.visitChildren(this); + } + } + + public final PortListContext portList() throws RecognitionException { + PortListContext _localctx = new PortListContext(_ctx, getState()); + enterRule(_localctx, 98, RULE_portList); + int _la; + try { + enterOuterAlt(_localctx, 1); + { + setState(405); + _errHandler.sync(this); + _la = _input.LA(1); + do { + { + { + setState(404); + port(); + } + } + setState(407); + _errHandler.sync(this); + _la = _input.LA(1); + } while ( (((_la) & ~0x3f) == 0 && ((1L << _la) & 266287972352L) != 0) ); + } + } + catch (RecognitionException re) { + _localctx.exception = re; + _errHandler.reportError(this, re); + _errHandler.recover(this, re); + } + finally { + exitRule(); + } + return _localctx; + } + + @SuppressWarnings("CheckReturnValue") + public static class PortContext extends ParserRuleContext { + public TerminalNode UNQUOTED_TEXT() { return getToken(DockerParser.UNQUOTED_TEXT, 0); } + public TerminalNode ENV_VAR() { return getToken(DockerParser.ENV_VAR, 0); } + public TerminalNode COMMAND_SUBST() { return getToken(DockerParser.COMMAND_SUBST, 0); } + public TerminalNode BACKTICK_SUBST() { return getToken(DockerParser.BACKTICK_SUBST, 0); } + public TerminalNode SPECIAL_VAR() { return getToken(DockerParser.SPECIAL_VAR, 0); } + public PortContext(ParserRuleContext parent, int invokingState) { + super(parent, invokingState); + } + @Override public int getRuleIndex() { return RULE_port; } + @Override + public void enterRule(ParseTreeListener listener) { + if ( listener instanceof DockerParserListener ) ((DockerParserListener)listener).enterPort(this); + } + @Override + public void exitRule(ParseTreeListener listener) { + if ( listener instanceof DockerParserListener ) ((DockerParserListener)listener).exitPort(this); + } + @Override + public T accept(ParseTreeVisitor visitor) { + if ( visitor instanceof DockerParserVisitor ) return ((DockerParserVisitor)visitor).visitPort(this); + else return visitor.visitChildren(this); + } + } + + public final PortContext port() throws RecognitionException { + PortContext _localctx = new PortContext(_ctx, getState()); + enterRule(_localctx, 100, RULE_port); + int _la; + try { + enterOuterAlt(_localctx, 1); + { + setState(409); + _la = _input.LA(1); + if ( !((((_la) & ~0x3f) == 0 && ((1L << _la) & 266287972352L) != 0)) ) { + _errHandler.recoverInline(this); + } + else { + if ( _input.LA(1)==Token.EOF ) matchedEOF = true; + _errHandler.reportMatch(this); + consume(); + } + } + } + catch (RecognitionException re) { + _localctx.exception = re; + _errHandler.reportError(this, re); + _errHandler.recover(this, re); + } + finally { + exitRule(); + } + return _localctx; + } + + @SuppressWarnings("CheckReturnValue") + public static class EnvPairsContext extends ParserRuleContext { + public List envPair() { + return getRuleContexts(EnvPairContext.class); + } + public EnvPairContext envPair(int i) { + return getRuleContext(EnvPairContext.class,i); + } + public EnvPairsContext(ParserRuleContext parent, int invokingState) { + super(parent, invokingState); + } + @Override public int getRuleIndex() { return RULE_envPairs; } + @Override + public void enterRule(ParseTreeListener listener) { + if ( listener instanceof DockerParserListener ) ((DockerParserListener)listener).enterEnvPairs(this); + } + @Override + public void exitRule(ParseTreeListener listener) { + if ( listener instanceof DockerParserListener ) ((DockerParserListener)listener).exitEnvPairs(this); + } + @Override + public T accept(ParseTreeVisitor visitor) { + if ( visitor instanceof DockerParserVisitor ) return ((DockerParserVisitor)visitor).visitEnvPairs(this); + else return visitor.visitChildren(this); + } + } + + public final EnvPairsContext envPairs() throws RecognitionException { + EnvPairsContext _localctx = new EnvPairsContext(_ctx, getState()); + enterRule(_localctx, 102, RULE_envPairs); + int _la; + try { + enterOuterAlt(_localctx, 1); + { + setState(412); + _errHandler.sync(this); + _la = _input.LA(1); + do { + { + { + setState(411); + envPair(); + } + } + setState(414); + _errHandler.sync(this); + _la = _input.LA(1); + } while ( _la==UNQUOTED_TEXT ); + } + } + catch (RecognitionException re) { + _localctx.exception = re; + _errHandler.reportError(this, re); + _errHandler.recover(this, re); + } + finally { + exitRule(); + } + return _localctx; + } + + @SuppressWarnings("CheckReturnValue") + public static class EnvPairContext extends ParserRuleContext { + public EnvKeyContext envKey() { + return getRuleContext(EnvKeyContext.class,0); + } + public TerminalNode EQUALS() { return getToken(DockerParser.EQUALS, 0); } + public EnvValueEqualsContext envValueEquals() { + return getRuleContext(EnvValueEqualsContext.class,0); + } + public EnvValueSpaceContext envValueSpace() { + return getRuleContext(EnvValueSpaceContext.class,0); + } + public EnvPairContext(ParserRuleContext parent, int invokingState) { + super(parent, invokingState); + } + @Override public int getRuleIndex() { return RULE_envPair; } + @Override + public void enterRule(ParseTreeListener listener) { + if ( listener instanceof DockerParserListener ) ((DockerParserListener)listener).enterEnvPair(this); + } + @Override + public void exitRule(ParseTreeListener listener) { + if ( listener instanceof DockerParserListener ) ((DockerParserListener)listener).exitEnvPair(this); + } + @Override + public T accept(ParseTreeVisitor visitor) { + if ( visitor instanceof DockerParserVisitor ) return ((DockerParserVisitor)visitor).visitEnvPair(this); + else return visitor.visitChildren(this); + } + } + + public final EnvPairContext envPair() throws RecognitionException { + EnvPairContext _localctx = new EnvPairContext(_ctx, getState()); + enterRule(_localctx, 104, RULE_envPair); + try { + setState(423); + _errHandler.sync(this); + switch ( getInterpreter().adaptivePredict(_input,36,_ctx) ) { + case 1: + enterOuterAlt(_localctx, 1); + { + setState(416); + envKey(); + setState(417); + match(EQUALS); + setState(418); + envValueEquals(); + } + break; + case 2: + enterOuterAlt(_localctx, 2); + { + setState(420); + envKey(); + setState(421); + envValueSpace(); + } + break; + } + } + catch (RecognitionException re) { + _localctx.exception = re; + _errHandler.reportError(this, re); + _errHandler.recover(this, re); + } + finally { + exitRule(); + } + return _localctx; + } + + @SuppressWarnings("CheckReturnValue") + public static class EnvKeyContext extends ParserRuleContext { + public TerminalNode UNQUOTED_TEXT() { return getToken(DockerParser.UNQUOTED_TEXT, 0); } + public EnvKeyContext(ParserRuleContext parent, int invokingState) { + super(parent, invokingState); + } + @Override public int getRuleIndex() { return RULE_envKey; } + @Override + public void enterRule(ParseTreeListener listener) { + if ( listener instanceof DockerParserListener ) ((DockerParserListener)listener).enterEnvKey(this); + } + @Override + public void exitRule(ParseTreeListener listener) { + if ( listener instanceof DockerParserListener ) ((DockerParserListener)listener).exitEnvKey(this); + } + @Override + public T accept(ParseTreeVisitor visitor) { + if ( visitor instanceof DockerParserVisitor ) return ((DockerParserVisitor)visitor).visitEnvKey(this); + else return visitor.visitChildren(this); + } + } + + public final EnvKeyContext envKey() throws RecognitionException { + EnvKeyContext _localctx = new EnvKeyContext(_ctx, getState()); + enterRule(_localctx, 106, RULE_envKey); + try { + enterOuterAlt(_localctx, 1); + { + setState(425); + match(UNQUOTED_TEXT); + } + } + catch (RecognitionException re) { + _localctx.exception = re; + _errHandler.reportError(this, re); + _errHandler.recover(this, re); + } + finally { + exitRule(); + } + return _localctx; + } + + @SuppressWarnings("CheckReturnValue") + public static class EnvValueEqualsContext extends ParserRuleContext { + public EnvTextEqualsContext envTextEquals() { + return getRuleContext(EnvTextEqualsContext.class,0); + } + public EnvValueEqualsContext(ParserRuleContext parent, int invokingState) { + super(parent, invokingState); + } + @Override public int getRuleIndex() { return RULE_envValueEquals; } + @Override + public void enterRule(ParseTreeListener listener) { + if ( listener instanceof DockerParserListener ) ((DockerParserListener)listener).enterEnvValueEquals(this); + } + @Override + public void exitRule(ParseTreeListener listener) { + if ( listener instanceof DockerParserListener ) ((DockerParserListener)listener).exitEnvValueEquals(this); + } + @Override + public T accept(ParseTreeVisitor visitor) { + if ( visitor instanceof DockerParserVisitor ) return ((DockerParserVisitor)visitor).visitEnvValueEquals(this); + else return visitor.visitChildren(this); + } + } + + public final EnvValueEqualsContext envValueEquals() throws RecognitionException { + EnvValueEqualsContext _localctx = new EnvValueEqualsContext(_ctx, getState()); + enterRule(_localctx, 108, RULE_envValueEquals); + try { + enterOuterAlt(_localctx, 1); + { + setState(427); + envTextEquals(); + } + } + catch (RecognitionException re) { + _localctx.exception = re; + _errHandler.reportError(this, re); + _errHandler.recover(this, re); + } + finally { + exitRule(); + } + return _localctx; + } + + @SuppressWarnings("CheckReturnValue") + public static class EnvValueSpaceContext extends ParserRuleContext { + public TextContext text() { + return getRuleContext(TextContext.class,0); + } + public EnvValueSpaceContext(ParserRuleContext parent, int invokingState) { + super(parent, invokingState); + } + @Override public int getRuleIndex() { return RULE_envValueSpace; } + @Override + public void enterRule(ParseTreeListener listener) { + if ( listener instanceof DockerParserListener ) ((DockerParserListener)listener).enterEnvValueSpace(this); + } + @Override + public void exitRule(ParseTreeListener listener) { + if ( listener instanceof DockerParserListener ) ((DockerParserListener)listener).exitEnvValueSpace(this); + } + @Override + public T accept(ParseTreeVisitor visitor) { + if ( visitor instanceof DockerParserVisitor ) return ((DockerParserVisitor)visitor).visitEnvValueSpace(this); + else return visitor.visitChildren(this); + } + } + + public final EnvValueSpaceContext envValueSpace() throws RecognitionException { + EnvValueSpaceContext _localctx = new EnvValueSpaceContext(_ctx, getState()); + enterRule(_localctx, 110, RULE_envValueSpace); + try { + enterOuterAlt(_localctx, 1); + { + setState(429); + text(); + } + } + catch (RecognitionException re) { + _localctx.exception = re; + _errHandler.reportError(this, re); + _errHandler.recover(this, re); + } + finally { + exitRule(); + } + return _localctx; + } + + @SuppressWarnings("CheckReturnValue") + public static class EnvTextEqualsContext extends ParserRuleContext { + public List envTextElementEquals() { + return getRuleContexts(EnvTextElementEqualsContext.class); + } + public EnvTextElementEqualsContext envTextElementEquals(int i) { + return getRuleContext(EnvTextElementEqualsContext.class,i); + } + public EnvTextEqualsContext(ParserRuleContext parent, int invokingState) { + super(parent, invokingState); + } + @Override public int getRuleIndex() { return RULE_envTextEquals; } + @Override + public void enterRule(ParseTreeListener listener) { + if ( listener instanceof DockerParserListener ) ((DockerParserListener)listener).enterEnvTextEquals(this); + } + @Override + public void exitRule(ParseTreeListener listener) { + if ( listener instanceof DockerParserListener ) ((DockerParserListener)listener).exitEnvTextEquals(this); + } + @Override + public T accept(ParseTreeVisitor visitor) { + if ( visitor instanceof DockerParserVisitor ) return ((DockerParserVisitor)visitor).visitEnvTextEquals(this); + else return visitor.visitChildren(this); + } + } + + public final EnvTextEqualsContext envTextEquals() throws RecognitionException { + EnvTextEqualsContext _localctx = new EnvTextEqualsContext(_ctx, getState()); + enterRule(_localctx, 112, RULE_envTextEquals); + try { + int _alt; + enterOuterAlt(_localctx, 1); + { + setState(432); + _errHandler.sync(this); + _alt = 1; + do { + switch (_alt) { + case 1: + { + { + setState(431); + envTextElementEquals(); + } + } + break; + default: + throw new NoViableAltException(this); + } + setState(434); + _errHandler.sync(this); + _alt = getInterpreter().adaptivePredict(_input,37,_ctx); + } while ( _alt!=2 && _alt!=org.antlr.v4.runtime.atn.ATN.INVALID_ALT_NUMBER ); + } + } + catch (RecognitionException re) { + _localctx.exception = re; + _errHandler.reportError(this, re); + _errHandler.recover(this, re); + } + finally { + exitRule(); + } + return _localctx; + } + + @SuppressWarnings("CheckReturnValue") + public static class EnvTextElementEqualsContext extends ParserRuleContext { + public TerminalNode UNQUOTED_TEXT() { return getToken(DockerParser.UNQUOTED_TEXT, 0); } + public TerminalNode DOUBLE_QUOTED_STRING() { return getToken(DockerParser.DOUBLE_QUOTED_STRING, 0); } + public TerminalNode SINGLE_QUOTED_STRING() { return getToken(DockerParser.SINGLE_QUOTED_STRING, 0); } + public TerminalNode ENV_VAR() { return getToken(DockerParser.ENV_VAR, 0); } + public TerminalNode COMMAND_SUBST() { return getToken(DockerParser.COMMAND_SUBST, 0); } + public TerminalNode BACKTICK_SUBST() { return getToken(DockerParser.BACKTICK_SUBST, 0); } + public TerminalNode SPECIAL_VAR() { return getToken(DockerParser.SPECIAL_VAR, 0); } + public EnvTextElementEqualsContext(ParserRuleContext parent, int invokingState) { + super(parent, invokingState); + } + @Override public int getRuleIndex() { return RULE_envTextElementEquals; } + @Override + public void enterRule(ParseTreeListener listener) { + if ( listener instanceof DockerParserListener ) ((DockerParserListener)listener).enterEnvTextElementEquals(this); + } + @Override + public void exitRule(ParseTreeListener listener) { + if ( listener instanceof DockerParserListener ) ((DockerParserListener)listener).exitEnvTextElementEquals(this); + } + @Override + public T accept(ParseTreeVisitor visitor) { + if ( visitor instanceof DockerParserVisitor ) return ((DockerParserVisitor)visitor).visitEnvTextElementEquals(this); + else return visitor.visitChildren(this); + } + } + + public final EnvTextElementEqualsContext envTextElementEquals() throws RecognitionException { + EnvTextElementEqualsContext _localctx = new EnvTextElementEqualsContext(_ctx, getState()); + enterRule(_localctx, 114, RULE_envTextElementEquals); + int _la; + try { + enterOuterAlt(_localctx, 1); + { + setState(436); + _la = _input.LA(1); + if ( !((((_la) & ~0x3f) == 0 && ((1L << _la) & 272730423296L) != 0)) ) { + _errHandler.recoverInline(this); + } + else { + if ( _input.LA(1)==Token.EOF ) matchedEOF = true; + _errHandler.reportMatch(this); + consume(); + } + } + } + catch (RecognitionException re) { + _localctx.exception = re; + _errHandler.reportError(this, re); + _errHandler.recover(this, re); + } + finally { + exitRule(); + } + return _localctx; + } + + @SuppressWarnings("CheckReturnValue") + public static class SourceListContext extends ParserRuleContext { + public List sourcePath() { + return getRuleContexts(SourcePathContext.class); + } + public SourcePathContext sourcePath(int i) { + return getRuleContext(SourcePathContext.class,i); + } + public SourceListContext(ParserRuleContext parent, int invokingState) { + super(parent, invokingState); + } + @Override public int getRuleIndex() { return RULE_sourceList; } + @Override + public void enterRule(ParseTreeListener listener) { + if ( listener instanceof DockerParserListener ) ((DockerParserListener)listener).enterSourceList(this); + } + @Override + public void exitRule(ParseTreeListener listener) { + if ( listener instanceof DockerParserListener ) ((DockerParserListener)listener).exitSourceList(this); + } + @Override + public T accept(ParseTreeVisitor visitor) { + if ( visitor instanceof DockerParserVisitor ) return ((DockerParserVisitor)visitor).visitSourceList(this); + else return visitor.visitChildren(this); + } + } + + public final SourceListContext sourceList() throws RecognitionException { + SourceListContext _localctx = new SourceListContext(_ctx, getState()); + enterRule(_localctx, 116, RULE_sourceList); + try { + int _alt; + enterOuterAlt(_localctx, 1); + { + setState(439); + _errHandler.sync(this); + _alt = 1; + do { + switch (_alt) { + case 1: + { + { + setState(438); + sourcePath(); + } + } + break; + default: + throw new NoViableAltException(this); + } + setState(441); + _errHandler.sync(this); + _alt = getInterpreter().adaptivePredict(_input,38,_ctx); + } while ( _alt!=2 && _alt!=org.antlr.v4.runtime.atn.ATN.INVALID_ALT_NUMBER ); + } + } + catch (RecognitionException re) { + _localctx.exception = re; + _errHandler.reportError(this, re); + _errHandler.recover(this, re); + } + finally { + exitRule(); + } + return _localctx; + } + + @SuppressWarnings("CheckReturnValue") + public static class SourcePathContext extends ParserRuleContext { + public TerminalNode UNQUOTED_TEXT() { return getToken(DockerParser.UNQUOTED_TEXT, 0); } + public TerminalNode DOUBLE_QUOTED_STRING() { return getToken(DockerParser.DOUBLE_QUOTED_STRING, 0); } + public TerminalNode SINGLE_QUOTED_STRING() { return getToken(DockerParser.SINGLE_QUOTED_STRING, 0); } + public TerminalNode ENV_VAR() { return getToken(DockerParser.ENV_VAR, 0); } + public TerminalNode COMMAND_SUBST() { return getToken(DockerParser.COMMAND_SUBST, 0); } + public TerminalNode BACKTICK_SUBST() { return getToken(DockerParser.BACKTICK_SUBST, 0); } + public TerminalNode SPECIAL_VAR() { return getToken(DockerParser.SPECIAL_VAR, 0); } + public SourcePathContext(ParserRuleContext parent, int invokingState) { + super(parent, invokingState); + } + @Override public int getRuleIndex() { return RULE_sourcePath; } + @Override + public void enterRule(ParseTreeListener listener) { + if ( listener instanceof DockerParserListener ) ((DockerParserListener)listener).enterSourcePath(this); + } + @Override + public void exitRule(ParseTreeListener listener) { + if ( listener instanceof DockerParserListener ) ((DockerParserListener)listener).exitSourcePath(this); + } + @Override + public T accept(ParseTreeVisitor visitor) { + if ( visitor instanceof DockerParserVisitor ) return ((DockerParserVisitor)visitor).visitSourcePath(this); + else return visitor.visitChildren(this); + } + } + + public final SourcePathContext sourcePath() throws RecognitionException { + SourcePathContext _localctx = new SourcePathContext(_ctx, getState()); + enterRule(_localctx, 118, RULE_sourcePath); + int _la; + try { + enterOuterAlt(_localctx, 1); + { + setState(443); + _la = _input.LA(1); + if ( !((((_la) & ~0x3f) == 0 && ((1L << _la) & 272730423296L) != 0)) ) { + _errHandler.recoverInline(this); + } + else { + if ( _input.LA(1)==Token.EOF ) matchedEOF = true; + _errHandler.reportMatch(this); + consume(); + } + } + } + catch (RecognitionException re) { + _localctx.exception = re; + _errHandler.reportError(this, re); + _errHandler.recover(this, re); + } + finally { + exitRule(); + } + return _localctx; + } + + @SuppressWarnings("CheckReturnValue") + public static class DestinationContext extends ParserRuleContext { + public DestinationPathContext destinationPath() { + return getRuleContext(DestinationPathContext.class,0); + } + public DestinationContext(ParserRuleContext parent, int invokingState) { + super(parent, invokingState); + } + @Override public int getRuleIndex() { return RULE_destination; } + @Override + public void enterRule(ParseTreeListener listener) { + if ( listener instanceof DockerParserListener ) ((DockerParserListener)listener).enterDestination(this); + } + @Override + public void exitRule(ParseTreeListener listener) { + if ( listener instanceof DockerParserListener ) ((DockerParserListener)listener).exitDestination(this); + } + @Override + public T accept(ParseTreeVisitor visitor) { + if ( visitor instanceof DockerParserVisitor ) return ((DockerParserVisitor)visitor).visitDestination(this); + else return visitor.visitChildren(this); + } + } + + public final DestinationContext destination() throws RecognitionException { + DestinationContext _localctx = new DestinationContext(_ctx, getState()); + enterRule(_localctx, 120, RULE_destination); + try { + enterOuterAlt(_localctx, 1); + { + setState(445); + destinationPath(); + } + } + catch (RecognitionException re) { + _localctx.exception = re; + _errHandler.reportError(this, re); + _errHandler.recover(this, re); + } + finally { + exitRule(); + } + return _localctx; + } + + @SuppressWarnings("CheckReturnValue") + public static class DestinationPathContext extends ParserRuleContext { + public TerminalNode UNQUOTED_TEXT() { return getToken(DockerParser.UNQUOTED_TEXT, 0); } + public TerminalNode DOUBLE_QUOTED_STRING() { return getToken(DockerParser.DOUBLE_QUOTED_STRING, 0); } + public TerminalNode SINGLE_QUOTED_STRING() { return getToken(DockerParser.SINGLE_QUOTED_STRING, 0); } + public TerminalNode ENV_VAR() { return getToken(DockerParser.ENV_VAR, 0); } + public TerminalNode COMMAND_SUBST() { return getToken(DockerParser.COMMAND_SUBST, 0); } + public TerminalNode BACKTICK_SUBST() { return getToken(DockerParser.BACKTICK_SUBST, 0); } + public TerminalNode SPECIAL_VAR() { return getToken(DockerParser.SPECIAL_VAR, 0); } + public DestinationPathContext(ParserRuleContext parent, int invokingState) { + super(parent, invokingState); + } + @Override public int getRuleIndex() { return RULE_destinationPath; } + @Override + public void enterRule(ParseTreeListener listener) { + if ( listener instanceof DockerParserListener ) ((DockerParserListener)listener).enterDestinationPath(this); + } + @Override + public void exitRule(ParseTreeListener listener) { + if ( listener instanceof DockerParserListener ) ((DockerParserListener)listener).exitDestinationPath(this); + } + @Override + public T accept(ParseTreeVisitor visitor) { + if ( visitor instanceof DockerParserVisitor ) return ((DockerParserVisitor)visitor).visitDestinationPath(this); + else return visitor.visitChildren(this); + } + } + + public final DestinationPathContext destinationPath() throws RecognitionException { + DestinationPathContext _localctx = new DestinationPathContext(_ctx, getState()); + enterRule(_localctx, 122, RULE_destinationPath); + int _la; + try { + enterOuterAlt(_localctx, 1); + { + setState(447); + _la = _input.LA(1); + if ( !((((_la) & ~0x3f) == 0 && ((1L << _la) & 272730423296L) != 0)) ) { + _errHandler.recoverInline(this); + } + else { + if ( _input.LA(1)==Token.EOF ) matchedEOF = true; + _errHandler.reportMatch(this); + consume(); + } + } + } + catch (RecognitionException re) { + _localctx.exception = re; + _errHandler.reportError(this, re); + _errHandler.recover(this, re); + } + finally { + exitRule(); + } + return _localctx; + } + + @SuppressWarnings("CheckReturnValue") + public static class PathContext extends ParserRuleContext { + public TextContext text() { + return getRuleContext(TextContext.class,0); + } + public PathContext(ParserRuleContext parent, int invokingState) { + super(parent, invokingState); + } + @Override public int getRuleIndex() { return RULE_path; } + @Override + public void enterRule(ParseTreeListener listener) { + if ( listener instanceof DockerParserListener ) ((DockerParserListener)listener).enterPath(this); + } + @Override + public void exitRule(ParseTreeListener listener) { + if ( listener instanceof DockerParserListener ) ((DockerParserListener)listener).exitPath(this); + } + @Override + public T accept(ParseTreeVisitor visitor) { + if ( visitor instanceof DockerParserVisitor ) return ((DockerParserVisitor)visitor).visitPath(this); + else return visitor.visitChildren(this); + } + } + + public final PathContext path() throws RecognitionException { + PathContext _localctx = new PathContext(_ctx, getState()); + enterRule(_localctx, 124, RULE_path); + try { + enterOuterAlt(_localctx, 1); + { + setState(449); + text(); + } + } + catch (RecognitionException re) { + _localctx.exception = re; + _errHandler.reportError(this, re); + _errHandler.recover(this, re); + } + finally { + exitRule(); + } + return _localctx; + } + + @SuppressWarnings("CheckReturnValue") + public static class PathListContext extends ParserRuleContext { + public List volumePath() { + return getRuleContexts(VolumePathContext.class); + } + public VolumePathContext volumePath(int i) { + return getRuleContext(VolumePathContext.class,i); + } + public PathListContext(ParserRuleContext parent, int invokingState) { + super(parent, invokingState); + } + @Override public int getRuleIndex() { return RULE_pathList; } + @Override + public void enterRule(ParseTreeListener listener) { + if ( listener instanceof DockerParserListener ) ((DockerParserListener)listener).enterPathList(this); + } + @Override + public void exitRule(ParseTreeListener listener) { + if ( listener instanceof DockerParserListener ) ((DockerParserListener)listener).exitPathList(this); + } + @Override + public T accept(ParseTreeVisitor visitor) { + if ( visitor instanceof DockerParserVisitor ) return ((DockerParserVisitor)visitor).visitPathList(this); + else return visitor.visitChildren(this); + } + } + + public final PathListContext pathList() throws RecognitionException { + PathListContext _localctx = new PathListContext(_ctx, getState()); + enterRule(_localctx, 126, RULE_pathList); + int _la; + try { + enterOuterAlt(_localctx, 1); + { + setState(452); + _errHandler.sync(this); + _la = _input.LA(1); + do { + { + { + setState(451); + volumePath(); + } + } + setState(454); + _errHandler.sync(this); + _la = _input.LA(1); + } while ( (((_la) & ~0x3f) == 0 && ((1L << _la) & 272730423296L) != 0) ); + } + } + catch (RecognitionException re) { + _localctx.exception = re; + _errHandler.reportError(this, re); + _errHandler.recover(this, re); + } + finally { + exitRule(); + } + return _localctx; + } + + @SuppressWarnings("CheckReturnValue") + public static class VolumePathContext extends ParserRuleContext { + public TerminalNode UNQUOTED_TEXT() { return getToken(DockerParser.UNQUOTED_TEXT, 0); } + public TerminalNode DOUBLE_QUOTED_STRING() { return getToken(DockerParser.DOUBLE_QUOTED_STRING, 0); } + public TerminalNode SINGLE_QUOTED_STRING() { return getToken(DockerParser.SINGLE_QUOTED_STRING, 0); } + public TerminalNode ENV_VAR() { return getToken(DockerParser.ENV_VAR, 0); } + public TerminalNode COMMAND_SUBST() { return getToken(DockerParser.COMMAND_SUBST, 0); } + public TerminalNode BACKTICK_SUBST() { return getToken(DockerParser.BACKTICK_SUBST, 0); } + public TerminalNode SPECIAL_VAR() { return getToken(DockerParser.SPECIAL_VAR, 0); } + public VolumePathContext(ParserRuleContext parent, int invokingState) { + super(parent, invokingState); + } + @Override public int getRuleIndex() { return RULE_volumePath; } + @Override + public void enterRule(ParseTreeListener listener) { + if ( listener instanceof DockerParserListener ) ((DockerParserListener)listener).enterVolumePath(this); + } + @Override + public void exitRule(ParseTreeListener listener) { + if ( listener instanceof DockerParserListener ) ((DockerParserListener)listener).exitVolumePath(this); + } + @Override + public T accept(ParseTreeVisitor visitor) { + if ( visitor instanceof DockerParserVisitor ) return ((DockerParserVisitor)visitor).visitVolumePath(this); + else return visitor.visitChildren(this); + } + } + + public final VolumePathContext volumePath() throws RecognitionException { + VolumePathContext _localctx = new VolumePathContext(_ctx, getState()); + enterRule(_localctx, 128, RULE_volumePath); + int _la; + try { + enterOuterAlt(_localctx, 1); + { + setState(456); + _la = _input.LA(1); + if ( !((((_la) & ~0x3f) == 0 && ((1L << _la) & 272730423296L) != 0)) ) { + _errHandler.recoverInline(this); + } + else { + if ( _input.LA(1)==Token.EOF ) matchedEOF = true; + _errHandler.reportMatch(this); + consume(); + } + } + } + catch (RecognitionException re) { + _localctx.exception = re; + _errHandler.reportError(this, re); + _errHandler.recover(this, re); + } + finally { + exitRule(); + } + return _localctx; + } + + @SuppressWarnings("CheckReturnValue") + public static class UserSpecContext extends ParserRuleContext { + public TextContext text() { + return getRuleContext(TextContext.class,0); + } + public UserSpecContext(ParserRuleContext parent, int invokingState) { + super(parent, invokingState); + } + @Override public int getRuleIndex() { return RULE_userSpec; } + @Override + public void enterRule(ParseTreeListener listener) { + if ( listener instanceof DockerParserListener ) ((DockerParserListener)listener).enterUserSpec(this); + } + @Override + public void exitRule(ParseTreeListener listener) { + if ( listener instanceof DockerParserListener ) ((DockerParserListener)listener).exitUserSpec(this); + } + @Override + public T accept(ParseTreeVisitor visitor) { + if ( visitor instanceof DockerParserVisitor ) return ((DockerParserVisitor)visitor).visitUserSpec(this); + else return visitor.visitChildren(this); + } + } + + public final UserSpecContext userSpec() throws RecognitionException { + UserSpecContext _localctx = new UserSpecContext(_ctx, getState()); + enterRule(_localctx, 130, RULE_userSpec); + try { + enterOuterAlt(_localctx, 1); + { + setState(458); + text(); + } + } + catch (RecognitionException re) { + _localctx.exception = re; + _errHandler.reportError(this, re); + _errHandler.recover(this, re); + } + finally { + exitRule(); + } + return _localctx; + } + + @SuppressWarnings("CheckReturnValue") + public static class ArgNameContext extends ParserRuleContext { + public TerminalNode UNQUOTED_TEXT() { return getToken(DockerParser.UNQUOTED_TEXT, 0); } + public ArgNameContext(ParserRuleContext parent, int invokingState) { + super(parent, invokingState); + } + @Override public int getRuleIndex() { return RULE_argName; } + @Override + public void enterRule(ParseTreeListener listener) { + if ( listener instanceof DockerParserListener ) ((DockerParserListener)listener).enterArgName(this); + } + @Override + public void exitRule(ParseTreeListener listener) { + if ( listener instanceof DockerParserListener ) ((DockerParserListener)listener).exitArgName(this); + } + @Override + public T accept(ParseTreeVisitor visitor) { + if ( visitor instanceof DockerParserVisitor ) return ((DockerParserVisitor)visitor).visitArgName(this); + else return visitor.visitChildren(this); + } + } + + public final ArgNameContext argName() throws RecognitionException { + ArgNameContext _localctx = new ArgNameContext(_ctx, getState()); + enterRule(_localctx, 132, RULE_argName); + try { + enterOuterAlt(_localctx, 1); + { + setState(460); + match(UNQUOTED_TEXT); + } + } + catch (RecognitionException re) { + _localctx.exception = re; + _errHandler.reportError(this, re); + _errHandler.recover(this, re); + } + finally { + exitRule(); + } + return _localctx; + } + + @SuppressWarnings("CheckReturnValue") + public static class ArgValueContext extends ParserRuleContext { + public TextContext text() { + return getRuleContext(TextContext.class,0); + } + public ArgValueContext(ParserRuleContext parent, int invokingState) { + super(parent, invokingState); + } + @Override public int getRuleIndex() { return RULE_argValue; } + @Override + public void enterRule(ParseTreeListener listener) { + if ( listener instanceof DockerParserListener ) ((DockerParserListener)listener).enterArgValue(this); + } + @Override + public void exitRule(ParseTreeListener listener) { + if ( listener instanceof DockerParserListener ) ((DockerParserListener)listener).exitArgValue(this); + } + @Override + public T accept(ParseTreeVisitor visitor) { + if ( visitor instanceof DockerParserVisitor ) return ((DockerParserVisitor)visitor).visitArgValue(this); + else return visitor.visitChildren(this); + } + } + + public final ArgValueContext argValue() throws RecognitionException { + ArgValueContext _localctx = new ArgValueContext(_ctx, getState()); + enterRule(_localctx, 134, RULE_argValue); + try { + enterOuterAlt(_localctx, 1); + { + setState(462); + text(); + } + } + catch (RecognitionException re) { + _localctx.exception = re; + _errHandler.reportError(this, re); + _errHandler.recover(this, re); + } + finally { + exitRule(); + } + return _localctx; + } + + @SuppressWarnings("CheckReturnValue") + public static class SignalContext extends ParserRuleContext { + public TerminalNode UNQUOTED_TEXT() { return getToken(DockerParser.UNQUOTED_TEXT, 0); } + public SignalContext(ParserRuleContext parent, int invokingState) { + super(parent, invokingState); + } + @Override public int getRuleIndex() { return RULE_signal; } + @Override + public void enterRule(ParseTreeListener listener) { + if ( listener instanceof DockerParserListener ) ((DockerParserListener)listener).enterSignal(this); + } + @Override + public void exitRule(ParseTreeListener listener) { + if ( listener instanceof DockerParserListener ) ((DockerParserListener)listener).exitSignal(this); + } + @Override + public T accept(ParseTreeVisitor visitor) { + if ( visitor instanceof DockerParserVisitor ) return ((DockerParserVisitor)visitor).visitSignal(this); + else return visitor.visitChildren(this); + } + } + + public final SignalContext signal() throws RecognitionException { + SignalContext _localctx = new SignalContext(_ctx, getState()); + enterRule(_localctx, 136, RULE_signal); + try { + enterOuterAlt(_localctx, 1); + { + setState(464); + match(UNQUOTED_TEXT); + } + } + catch (RecognitionException re) { + _localctx.exception = re; + _errHandler.reportError(this, re); + _errHandler.recover(this, re); + } + finally { + exitRule(); + } + return _localctx; + } + + @SuppressWarnings("CheckReturnValue") + public static class TextContext extends ParserRuleContext { + public List textElement() { + return getRuleContexts(TextElementContext.class); + } + public TextElementContext textElement(int i) { + return getRuleContext(TextElementContext.class,i); + } + public TextContext(ParserRuleContext parent, int invokingState) { + super(parent, invokingState); + } + @Override public int getRuleIndex() { return RULE_text; } + @Override + public void enterRule(ParseTreeListener listener) { + if ( listener instanceof DockerParserListener ) ((DockerParserListener)listener).enterText(this); + } + @Override + public void exitRule(ParseTreeListener listener) { + if ( listener instanceof DockerParserListener ) ((DockerParserListener)listener).exitText(this); + } + @Override + public T accept(ParseTreeVisitor visitor) { + if ( visitor instanceof DockerParserVisitor ) return ((DockerParserVisitor)visitor).visitText(this); + else return visitor.visitChildren(this); + } + } + + public final TextContext text() throws RecognitionException { + TextContext _localctx = new TextContext(_ctx, getState()); + enterRule(_localctx, 138, RULE_text); + try { + int _alt; + enterOuterAlt(_localctx, 1); + { + setState(467); + _errHandler.sync(this); + _alt = 1; + do { + switch (_alt) { + case 1: + { + { + setState(466); + textElement(); + } + } + break; + default: + throw new NoViableAltException(this); + } + setState(469); + _errHandler.sync(this); + _alt = getInterpreter().adaptivePredict(_input,40,_ctx); + } while ( _alt!=2 && _alt!=org.antlr.v4.runtime.atn.ATN.INVALID_ALT_NUMBER ); + } + } + catch (RecognitionException re) { + _localctx.exception = re; + _errHandler.reportError(this, re); + _errHandler.recover(this, re); + } + finally { + exitRule(); + } + return _localctx; + } + + @SuppressWarnings("CheckReturnValue") + public static class TextElementContext extends ParserRuleContext { + public TerminalNode UNQUOTED_TEXT() { return getToken(DockerParser.UNQUOTED_TEXT, 0); } + public TerminalNode DOUBLE_QUOTED_STRING() { return getToken(DockerParser.DOUBLE_QUOTED_STRING, 0); } + public TerminalNode SINGLE_QUOTED_STRING() { return getToken(DockerParser.SINGLE_QUOTED_STRING, 0); } + public TerminalNode ENV_VAR() { return getToken(DockerParser.ENV_VAR, 0); } + public TerminalNode COMMAND_SUBST() { return getToken(DockerParser.COMMAND_SUBST, 0); } + public TerminalNode BACKTICK_SUBST() { return getToken(DockerParser.BACKTICK_SUBST, 0); } + public TerminalNode SPECIAL_VAR() { return getToken(DockerParser.SPECIAL_VAR, 0); } + public TerminalNode EQUALS() { return getToken(DockerParser.EQUALS, 0); } + public TerminalNode FLAG() { return getToken(DockerParser.FLAG, 0); } + public TerminalNode DASH_DASH() { return getToken(DockerParser.DASH_DASH, 0); } + public TerminalNode LBRACKET() { return getToken(DockerParser.LBRACKET, 0); } + public TerminalNode RBRACKET() { return getToken(DockerParser.RBRACKET, 0); } + public TerminalNode COMMA() { return getToken(DockerParser.COMMA, 0); } + public TextElementContext(ParserRuleContext parent, int invokingState) { + super(parent, invokingState); + } + @Override public int getRuleIndex() { return RULE_textElement; } + @Override + public void enterRule(ParseTreeListener listener) { + if ( listener instanceof DockerParserListener ) ((DockerParserListener)listener).enterTextElement(this); + } + @Override + public void exitRule(ParseTreeListener listener) { + if ( listener instanceof DockerParserListener ) ((DockerParserListener)listener).exitTextElement(this); + } + @Override + public T accept(ParseTreeVisitor visitor) { + if ( visitor instanceof DockerParserVisitor ) return ((DockerParserVisitor)visitor).visitTextElement(this); + else return visitor.visitChildren(this); + } + } + + public final TextElementContext textElement() throws RecognitionException { + TextElementContext _localctx = new TextElementContext(_ctx, getState()); + enterRule(_localctx, 140, RULE_textElement); + int _la; + try { + enterOuterAlt(_localctx, 1); + { + setState(471); + _la = _input.LA(1); + if ( !((((_la) & ~0x3f) == 0 && ((1L << _la) & 274844352512L) != 0)) ) { + _errHandler.recoverInline(this); + } + else { + if ( _input.LA(1)==Token.EOF ) matchedEOF = true; + _errHandler.reportMatch(this); + consume(); + } + } + } + catch (RecognitionException re) { + _localctx.exception = re; + _errHandler.reportError(this, re); + _errHandler.recover(this, re); + } + finally { + exitRule(); + } + return _localctx; + } + + public static final String _serializedATN = + "\u0004\u0001-\u01da\u0002\u0000\u0007\u0000\u0002\u0001\u0007\u0001\u0002"+ + "\u0002\u0007\u0002\u0002\u0003\u0007\u0003\u0002\u0004\u0007\u0004\u0002"+ + "\u0005\u0007\u0005\u0002\u0006\u0007\u0006\u0002\u0007\u0007\u0007\u0002"+ + "\b\u0007\b\u0002\t\u0007\t\u0002\n\u0007\n\u0002\u000b\u0007\u000b\u0002"+ + "\f\u0007\f\u0002\r\u0007\r\u0002\u000e\u0007\u000e\u0002\u000f\u0007\u000f"+ + "\u0002\u0010\u0007\u0010\u0002\u0011\u0007\u0011\u0002\u0012\u0007\u0012"+ + "\u0002\u0013\u0007\u0013\u0002\u0014\u0007\u0014\u0002\u0015\u0007\u0015"+ + "\u0002\u0016\u0007\u0016\u0002\u0017\u0007\u0017\u0002\u0018\u0007\u0018"+ + "\u0002\u0019\u0007\u0019\u0002\u001a\u0007\u001a\u0002\u001b\u0007\u001b"+ + "\u0002\u001c\u0007\u001c\u0002\u001d\u0007\u001d\u0002\u001e\u0007\u001e"+ + "\u0002\u001f\u0007\u001f\u0002 \u0007 \u0002!\u0007!\u0002\"\u0007\"\u0002"+ + "#\u0007#\u0002$\u0007$\u0002%\u0007%\u0002&\u0007&\u0002\'\u0007\'\u0002"+ + "(\u0007(\u0002)\u0007)\u0002*\u0007*\u0002+\u0007+\u0002,\u0007,\u0002"+ + "-\u0007-\u0002.\u0007.\u0002/\u0007/\u00020\u00070\u00021\u00071\u0002"+ + "2\u00072\u00023\u00073\u00024\u00074\u00025\u00075\u00026\u00076\u0002"+ + "7\u00077\u00028\u00078\u00029\u00079\u0002:\u0007:\u0002;\u0007;\u0002"+ + "<\u0007<\u0002=\u0007=\u0002>\u0007>\u0002?\u0007?\u0002@\u0007@\u0002"+ + "A\u0007A\u0002B\u0007B\u0002C\u0007C\u0002D\u0007D\u0002E\u0007E\u0002"+ + "F\u0007F\u0001\u0000\u0005\u0000\u0090\b\u0000\n\u0000\f\u0000\u0093\t"+ + "\u0000\u0001\u0000\u0001\u0000\u0004\u0000\u0097\b\u0000\u000b\u0000\f"+ + "\u0000\u0098\u0001\u0000\u0001\u0000\u0001\u0001\u0001\u0001\u0001\u0002"+ + "\u0005\u0002\u00a0\b\u0002\n\u0002\f\u0002\u00a3\t\u0002\u0001\u0003\u0001"+ + "\u0003\u0005\u0003\u00a7\b\u0003\n\u0003\f\u0003\u00aa\t\u0003\u0001\u0004"+ + "\u0001\u0004\u0001\u0004\u0001\u0004\u0001\u0004\u0001\u0004\u0001\u0004"+ + "\u0001\u0004\u0001\u0004\u0001\u0004\u0001\u0004\u0001\u0004\u0001\u0004"+ + "\u0001\u0004\u0001\u0004\u0001\u0004\u0001\u0004\u0003\u0004\u00bd\b\u0004"+ + "\u0001\u0005\u0001\u0005\u0003\u0005\u00c1\b\u0005\u0001\u0006\u0001\u0006"+ + "\u0003\u0006\u00c5\b\u0006\u0001\u0006\u0001\u0006\u0001\u0006\u0003\u0006"+ + "\u00ca\b\u0006\u0001\u0007\u0001\u0007\u0003\u0007\u00ce\b\u0007\u0001"+ + "\u0007\u0001\u0007\u0001\u0007\u0003\u0007\u00d3\b\u0007\u0001\b\u0001"+ + "\b\u0001\b\u0003\b\u00d8\b\b\u0001\t\u0001\t\u0001\t\u0001\n\u0001\n\u0001"+ + "\n\u0001\u000b\u0001\u000b\u0001\u000b\u0001\f\u0001\f\u0003\f\u00e5\b"+ + "\f\u0001\f\u0001\f\u0001\f\u0001\f\u0001\f\u0003\f\u00ec\b\f\u0001\r\u0001"+ + "\r\u0003\r\u00f0\b\r\u0001\r\u0001\r\u0001\r\u0001\r\u0001\r\u0003\r\u00f7"+ + "\b\r\u0001\u000e\u0001\u000e\u0001\u000e\u0003\u000e\u00fc\b\u000e\u0001"+ + "\u000f\u0001\u000f\u0001\u000f\u0003\u000f\u0101\b\u000f\u0001\u0010\u0001"+ + "\u0010\u0001\u0010\u0001\u0011\u0001\u0011\u0001\u0011\u0001\u0012\u0001"+ + "\u0012\u0001\u0012\u0001\u0012\u0003\u0012\u010d\b\u0012\u0001\u0013\u0001"+ + "\u0013\u0001\u0013\u0001\u0014\u0001\u0014\u0001\u0014\u0001\u0015\u0001"+ + "\u0015\u0001\u0015\u0001\u0015\u0003\u0015\u0119\b\u0015\u0001\u0015\u0001"+ + "\u0015\u0001\u0015\u0003\u0015\u011e\b\u0015\u0003\u0015\u0120\b\u0015"+ + "\u0001\u0016\u0004\u0016\u0123\b\u0016\u000b\u0016\f\u0016\u0124\u0001"+ + "\u0017\u0001\u0017\u0001\u0018\u0001\u0018\u0001\u0018\u0001\u0019\u0001"+ + "\u0019\u0001\u0019\u0001\u001a\u0004\u001a\u0130\b\u001a\u000b\u001a\f"+ + "\u001a\u0131\u0001\u001b\u0001\u001b\u0001\u001c\u0001\u001c\u0001\u001d"+ + "\u0001\u001d\u0001\u001e\u0004\u001e\u013b\b\u001e\u000b\u001e\f\u001e"+ + "\u013c\u0001\u001f\u0001\u001f\u0001 \u0001 \u0001 \u0004 \u0144\b \u000b"+ + " \f \u0145\u0001!\u0001!\u0005!\u014a\b!\n!\f!\u014d\t!\u0001!\u0001!"+ + "\u0005!\u0151\b!\n!\f!\u0154\t!\u0005!\u0156\b!\n!\f!\u0159\t!\u0001\""+ + "\u0001\"\u0001#\u0001#\u0001#\u0001$\u0005$\u0161\b$\n$\f$\u0164\t$\u0001"+ + "%\u0001%\u0001&\u0001&\u0003&\u016a\b&\u0001&\u0001&\u0001\'\u0001\'\u0001"+ + "\'\u0005\'\u0171\b\'\n\'\f\'\u0174\t\'\u0001(\u0001(\u0001)\u0001)\u0001"+ + "*\u0001*\u0001+\u0004+\u017d\b+\u000b+\f+\u017e\u0001,\u0001,\u0001,\u0001"+ + ",\u0001,\u0001,\u0001,\u0003,\u0188\b,\u0001-\u0001-\u0001.\u0001.\u0001"+ + "/\u0004/\u018f\b/\u000b/\f/\u0190\u00010\u00010\u00011\u00041\u0196\b"+ + "1\u000b1\f1\u0197\u00012\u00012\u00013\u00043\u019d\b3\u000b3\f3\u019e"+ + "\u00014\u00014\u00014\u00014\u00014\u00014\u00014\u00034\u01a8\b4\u0001"+ + "5\u00015\u00016\u00016\u00017\u00017\u00018\u00048\u01b1\b8\u000b8\f8"+ + "\u01b2\u00019\u00019\u0001:\u0004:\u01b8\b:\u000b:\f:\u01b9\u0001;\u0001"+ + ";\u0001<\u0001<\u0001=\u0001=\u0001>\u0001>\u0001?\u0004?\u01c5\b?\u000b"+ + "?\f?\u01c6\u0001@\u0001@\u0001A\u0001A\u0001B\u0001B\u0001C\u0001C\u0001"+ + "D\u0001D\u0001E\u0004E\u01d4\bE\u000bE\fE\u01d5\u0001F\u0001F\u0001F\u0000"+ + "\u0000G\u0000\u0002\u0004\u0006\b\n\f\u000e\u0010\u0012\u0014\u0016\u0018"+ + "\u001a\u001c\u001e \"$&(*,.02468:<>@BDFHJLNPRTVXZ\\^`bdfhjlnprtvxz|~\u0080"+ + "\u0082\u0084\u0086\u0088\u008a\u008c\u0000\u0005\u0001\u0000\u0019%\u0002"+ + "\u0000\'\',,\u0002\u0000\u001f %%\u0001\u0000!%\u0001\u0000\u001f%\u01cd"+ + "\u0000\u0091\u0001\u0000\u0000\u0000\u0002\u009c\u0001\u0000\u0000\u0000"+ + "\u0004\u00a1\u0001\u0000\u0000\u0000\u0006\u00a4\u0001\u0000\u0000\u0000"+ + "\b\u00bc\u0001\u0000\u0000\u0000\n\u00c0\u0001\u0000\u0000\u0000\f\u00c2"+ + "\u0001\u0000\u0000\u0000\u000e\u00cb\u0001\u0000\u0000\u0000\u0010\u00d4"+ + "\u0001\u0000\u0000\u0000\u0012\u00d9\u0001\u0000\u0000\u0000\u0014\u00dc"+ + "\u0001\u0000\u0000\u0000\u0016\u00df\u0001\u0000\u0000\u0000\u0018\u00e2"+ + "\u0001\u0000\u0000\u0000\u001a\u00ed\u0001\u0000\u0000\u0000\u001c\u00f8"+ + "\u0001\u0000\u0000\u0000\u001e\u00fd\u0001\u0000\u0000\u0000 \u0102\u0001"+ + "\u0000\u0000\u0000\"\u0105\u0001\u0000\u0000\u0000$\u0108\u0001\u0000"+ + "\u0000\u0000&\u010e\u0001\u0000\u0000\u0000(\u0111\u0001\u0000\u0000\u0000"+ + "*\u011f\u0001\u0000\u0000\u0000,\u0122\u0001\u0000\u0000\u0000.\u0126"+ + "\u0001\u0000\u0000\u00000\u0128\u0001\u0000\u0000\u00002\u012b\u0001\u0000"+ + "\u0000\u00004\u012f\u0001\u0000\u0000\u00006\u0133\u0001\u0000\u0000\u0000"+ + "8\u0135\u0001\u0000\u0000\u0000:\u0137\u0001\u0000\u0000\u0000<\u013a"+ + "\u0001\u0000\u0000\u0000>\u013e\u0001\u0000\u0000\u0000@\u0140\u0001\u0000"+ + "\u0000\u0000B\u0147\u0001\u0000\u0000\u0000D\u015a\u0001\u0000\u0000\u0000"+ + "F\u015c\u0001\u0000\u0000\u0000H\u0162\u0001\u0000\u0000\u0000J\u0165"+ + "\u0001\u0000\u0000\u0000L\u0167\u0001\u0000\u0000\u0000N\u016d\u0001\u0000"+ + "\u0000\u0000P\u0175\u0001\u0000\u0000\u0000R\u0177\u0001\u0000\u0000\u0000"+ + "T\u0179\u0001\u0000\u0000\u0000V\u017c\u0001\u0000\u0000\u0000X\u0187"+ + "\u0001\u0000\u0000\u0000Z\u0189\u0001\u0000\u0000\u0000\\\u018b\u0001"+ + "\u0000\u0000\u0000^\u018e\u0001\u0000\u0000\u0000`\u0192\u0001\u0000\u0000"+ + "\u0000b\u0195\u0001\u0000\u0000\u0000d\u0199\u0001\u0000\u0000\u0000f"+ + "\u019c\u0001\u0000\u0000\u0000h\u01a7\u0001\u0000\u0000\u0000j\u01a9\u0001"+ + "\u0000\u0000\u0000l\u01ab\u0001\u0000\u0000\u0000n\u01ad\u0001\u0000\u0000"+ + "\u0000p\u01b0\u0001\u0000\u0000\u0000r\u01b4\u0001\u0000\u0000\u0000t"+ + "\u01b7\u0001\u0000\u0000\u0000v\u01bb\u0001\u0000\u0000\u0000x\u01bd\u0001"+ + "\u0000\u0000\u0000z\u01bf\u0001\u0000\u0000\u0000|\u01c1\u0001\u0000\u0000"+ + "\u0000~\u01c4\u0001\u0000\u0000\u0000\u0080\u01c8\u0001\u0000\u0000\u0000"+ + "\u0082\u01ca\u0001\u0000\u0000\u0000\u0084\u01cc\u0001\u0000\u0000\u0000"+ + "\u0086\u01ce\u0001\u0000\u0000\u0000\u0088\u01d0\u0001\u0000\u0000\u0000"+ + "\u008a\u01d3\u0001\u0000\u0000\u0000\u008c\u01d7\u0001\u0000\u0000\u0000"+ + "\u008e\u0090\u0003\u0002\u0001\u0000\u008f\u008e\u0001\u0000\u0000\u0000"+ + "\u0090\u0093\u0001\u0000\u0000\u0000\u0091\u008f\u0001\u0000\u0000\u0000"+ + "\u0091\u0092\u0001\u0000\u0000\u0000\u0092\u0094\u0001\u0000\u0000\u0000"+ + "\u0093\u0091\u0001\u0000\u0000\u0000\u0094\u0096\u0003\u0004\u0002\u0000"+ + "\u0095\u0097\u0003\u0006\u0003\u0000\u0096\u0095\u0001\u0000\u0000\u0000"+ + "\u0097\u0098\u0001\u0000\u0000\u0000\u0098\u0096\u0001\u0000\u0000\u0000"+ + "\u0098\u0099\u0001\u0000\u0000\u0000\u0099\u009a\u0001\u0000\u0000\u0000"+ + "\u009a\u009b\u0005\u0000\u0000\u0001\u009b\u0001\u0001\u0000\u0000\u0000"+ + "\u009c\u009d\u0005\u0001\u0000\u0000\u009d\u0003\u0001\u0000\u0000\u0000"+ + "\u009e\u00a0\u0003$\u0012\u0000\u009f\u009e\u0001\u0000\u0000\u0000\u00a0"+ + "\u00a3\u0001\u0000\u0000\u0000\u00a1\u009f\u0001\u0000\u0000\u0000\u00a1"+ + "\u00a2\u0001\u0000\u0000\u0000\u00a2\u0005\u0001\u0000\u0000\u0000\u00a3"+ + "\u00a1\u0001\u0000\u0000\u0000\u00a4\u00a8\u0003\f\u0006\u0000\u00a5\u00a7"+ + "\u0003\b\u0004\u0000\u00a6\u00a5\u0001\u0000\u0000\u0000\u00a7\u00aa\u0001"+ + "\u0000\u0000\u0000\u00a8\u00a6\u0001\u0000\u0000\u0000\u00a8\u00a9\u0001"+ + "\u0000\u0000\u0000\u00a9\u0007\u0001\u0000\u0000\u0000\u00aa\u00a8\u0001"+ + "\u0000\u0000\u0000\u00ab\u00bd\u0003\u000e\u0007\u0000\u00ac\u00bd\u0003"+ + "\u0010\b\u0000\u00ad\u00bd\u0003\u0012\t\u0000\u00ae\u00bd\u0003\u0014"+ + "\n\u0000\u00af\u00bd\u0003\u0016\u000b\u0000\u00b0\u00bd\u0003\u0018\f"+ + "\u0000\u00b1\u00bd\u0003\u001a\r\u0000\u00b2\u00bd\u0003\u001c\u000e\u0000"+ + "\u00b3\u00bd\u0003\u001e\u000f\u0000\u00b4\u00bd\u0003 \u0010\u0000\u00b5"+ + "\u00bd\u0003\"\u0011\u0000\u00b6\u00bd\u0003$\u0012\u0000\u00b7\u00bd"+ + "\u0003&\u0013\u0000\u00b8\u00bd\u0003(\u0014\u0000\u00b9\u00bd\u0003*"+ + "\u0015\u0000\u00ba\u00bd\u00030\u0018\u0000\u00bb\u00bd\u00032\u0019\u0000"+ + "\u00bc\u00ab\u0001\u0000\u0000\u0000\u00bc\u00ac\u0001\u0000\u0000\u0000"+ + "\u00bc\u00ad\u0001\u0000\u0000\u0000\u00bc\u00ae\u0001\u0000\u0000\u0000"+ + "\u00bc\u00af\u0001\u0000\u0000\u0000\u00bc\u00b0\u0001\u0000\u0000\u0000"+ + "\u00bc\u00b1\u0001\u0000\u0000\u0000\u00bc\u00b2\u0001\u0000\u0000\u0000"+ + "\u00bc\u00b3\u0001\u0000\u0000\u0000\u00bc\u00b4\u0001\u0000\u0000\u0000"+ + "\u00bc\u00b5\u0001\u0000\u0000\u0000\u00bc\u00b6\u0001\u0000\u0000\u0000"+ + "\u00bc\u00b7\u0001\u0000\u0000\u0000\u00bc\u00b8\u0001\u0000\u0000\u0000"+ + "\u00bc\u00b9\u0001\u0000\u0000\u0000\u00bc\u00ba\u0001\u0000\u0000\u0000"+ + "\u00bc\u00bb\u0001\u0000\u0000\u0000\u00bd\t\u0001\u0000\u0000\u0000\u00be"+ + "\u00c1\u0003\f\u0006\u0000\u00bf\u00c1\u0003\b\u0004\u0000\u00c0\u00be"+ + "\u0001\u0000\u0000\u0000\u00c0\u00bf\u0001\u0000\u0000\u0000\u00c1\u000b"+ + "\u0001\u0000\u0000\u0000\u00c2\u00c4\u0005\u0003\u0000\u0000\u00c3\u00c5"+ + "\u00034\u001a\u0000\u00c4\u00c3\u0001\u0000\u0000\u0000\u00c4\u00c5\u0001"+ + "\u0000\u0000\u0000\u00c5\u00c6\u0001\u0000\u0000\u0000\u00c6\u00c9\u0003"+ + "R)\u0000\u00c7\u00c8\u0005\u0016\u0000\u0000\u00c8\u00ca\u0003T*\u0000"+ + "\u00c9\u00c7\u0001\u0000\u0000\u0000\u00c9\u00ca\u0001\u0000\u0000\u0000"+ + "\u00ca\r\u0001\u0000\u0000\u0000\u00cb\u00cd\u0005\u0004\u0000\u0000\u00cc"+ + "\u00ce\u00034\u001a\u0000\u00cd\u00cc\u0001\u0000\u0000\u0000\u00cd\u00ce"+ + "\u0001\u0000\u0000\u0000\u00ce\u00d2\u0001\u0000\u0000\u0000\u00cf\u00d3"+ + "\u00038\u001c\u0000\u00d0\u00d3\u0003:\u001d\u0000\u00d1\u00d3\u0003@"+ + " \u0000\u00d2\u00cf\u0001\u0000\u0000\u0000\u00d2\u00d0\u0001\u0000\u0000"+ + "\u0000\u00d2\u00d1\u0001\u0000\u0000\u0000\u00d3\u000f\u0001\u0000\u0000"+ + "\u0000\u00d4\u00d7\u0005\u0005\u0000\u0000\u00d5\u00d8\u00038\u001c\u0000"+ + "\u00d6\u00d8\u0003:\u001d\u0000\u00d7\u00d5\u0001\u0000\u0000\u0000\u00d7"+ + "\u00d6\u0001\u0000\u0000\u0000\u00d8\u0011\u0001\u0000\u0000\u0000\u00d9"+ + "\u00da\u0005\u0007\u0000\u0000\u00da\u00db\u0003V+\u0000\u00db\u0013\u0001"+ + "\u0000\u0000\u0000\u00dc\u00dd\u0005\b\u0000\u0000\u00dd\u00de\u0003b"+ + "1\u0000\u00de\u0015\u0001\u0000\u0000\u0000\u00df\u00e0\u0005\t\u0000"+ + "\u0000\u00e0\u00e1\u0003f3\u0000\u00e1\u0017\u0001\u0000\u0000\u0000\u00e2"+ + "\u00e4\u0005\n\u0000\u0000\u00e3\u00e5\u00034\u001a\u0000\u00e4\u00e3"+ + "\u0001\u0000\u0000\u0000\u00e4\u00e5\u0001\u0000\u0000\u0000\u00e5\u00eb"+ + "\u0001\u0000\u0000\u0000\u00e6\u00ec\u0003@ \u0000\u00e7\u00ec\u0003L"+ + "&\u0000\u00e8\u00e9\u0003t:\u0000\u00e9\u00ea\u0003x<\u0000\u00ea\u00ec"+ + "\u0001\u0000\u0000\u0000\u00eb\u00e6\u0001\u0000\u0000\u0000\u00eb\u00e7"+ + "\u0001\u0000\u0000\u0000\u00eb\u00e8\u0001\u0000\u0000\u0000\u00ec\u0019"+ + "\u0001\u0000\u0000\u0000\u00ed\u00ef\u0005\u000b\u0000\u0000\u00ee\u00f0"+ + "\u00034\u001a\u0000\u00ef\u00ee\u0001\u0000\u0000\u0000\u00ef\u00f0\u0001"+ + "\u0000\u0000\u0000\u00f0\u00f6\u0001\u0000\u0000\u0000\u00f1\u00f7\u0003"+ + "@ \u0000\u00f2\u00f7\u0003L&\u0000\u00f3\u00f4\u0003t:\u0000\u00f4\u00f5"+ + "\u0003x<\u0000\u00f5\u00f7\u0001\u0000\u0000\u0000\u00f6\u00f1\u0001\u0000"+ + "\u0000\u0000\u00f6\u00f2\u0001\u0000\u0000\u0000\u00f6\u00f3\u0001\u0000"+ + "\u0000\u0000\u00f7\u001b\u0001\u0000\u0000\u0000\u00f8\u00fb\u0005\f\u0000"+ + "\u0000\u00f9\u00fc\u00038\u001c\u0000\u00fa\u00fc\u0003:\u001d\u0000\u00fb"+ + "\u00f9\u0001\u0000\u0000\u0000\u00fb\u00fa\u0001\u0000\u0000\u0000\u00fc"+ + "\u001d\u0001\u0000\u0000\u0000\u00fd\u0100\u0005\r\u0000\u0000\u00fe\u0101"+ + "\u0003L&\u0000\u00ff\u0101\u0003~?\u0000\u0100\u00fe\u0001\u0000\u0000"+ + "\u0000\u0100\u00ff\u0001\u0000\u0000\u0000\u0101\u001f\u0001\u0000\u0000"+ + "\u0000\u0102\u0103\u0005\u000e\u0000\u0000\u0103\u0104\u0003\u0082A\u0000"+ + "\u0104!\u0001\u0000\u0000\u0000\u0105\u0106\u0005\u000f\u0000\u0000\u0106"+ + "\u0107\u0003|>\u0000\u0107#\u0001\u0000\u0000\u0000\u0108\u0109\u0005"+ + "\u0010\u0000\u0000\u0109\u010c\u0003\u0084B\u0000\u010a\u010b\u0005\u001c"+ + "\u0000\u0000\u010b\u010d\u0003\u0086C\u0000\u010c\u010a\u0001\u0000\u0000"+ + "\u0000\u010c\u010d\u0001\u0000\u0000\u0000\u010d%\u0001\u0000\u0000\u0000"+ + "\u010e\u010f\u0005\u0011\u0000\u0000\u010f\u0110\u0003\n\u0005\u0000\u0110"+ + "\'\u0001\u0000\u0000\u0000\u0111\u0112\u0005\u0012\u0000\u0000\u0112\u0113"+ + "\u0003\u0088D\u0000\u0113)\u0001\u0000\u0000\u0000\u0114\u0115\u0005\u0013"+ + "\u0000\u0000\u0115\u0120\u0005\u0006\u0000\u0000\u0116\u0118\u0005\u0013"+ + "\u0000\u0000\u0117\u0119\u0003,\u0016\u0000\u0118\u0117\u0001\u0000\u0000"+ + "\u0000\u0118\u0119\u0001\u0000\u0000\u0000\u0119\u011a\u0001\u0000\u0000"+ + "\u0000\u011a\u011d\u0005\u0005\u0000\u0000\u011b\u011e\u00038\u001c\u0000"+ + "\u011c\u011e\u0003:\u001d\u0000\u011d\u011b\u0001\u0000\u0000\u0000\u011d"+ + "\u011c\u0001\u0000\u0000\u0000\u011e\u0120\u0001\u0000\u0000\u0000\u011f"+ + "\u0114\u0001\u0000\u0000\u0000\u011f\u0116\u0001\u0000\u0000\u0000\u0120"+ + "+\u0001\u0000\u0000\u0000\u0121\u0123\u0003.\u0017\u0000\u0122\u0121\u0001"+ + "\u0000\u0000\u0000\u0123\u0124\u0001\u0000\u0000\u0000\u0124\u0122\u0001"+ + "\u0000\u0000\u0000\u0124\u0125\u0001\u0000\u0000\u0000\u0125-\u0001\u0000"+ + "\u0000\u0000\u0126\u0127\u0005\u001d\u0000\u0000\u0127/\u0001\u0000\u0000"+ + "\u0000\u0128\u0129\u0005\u0014\u0000\u0000\u0129\u012a\u0003L&\u0000\u012a"+ + "1\u0001\u0000\u0000\u0000\u012b\u012c\u0005\u0015\u0000\u0000\u012c\u012d"+ + "\u0003\u008aE\u0000\u012d3\u0001\u0000\u0000\u0000\u012e\u0130\u00036"+ + "\u001b\u0000\u012f\u012e\u0001\u0000\u0000\u0000\u0130\u0131\u0001\u0000"+ + "\u0000\u0000\u0131\u012f\u0001\u0000\u0000\u0000\u0131\u0132\u0001\u0000"+ + "\u0000\u0000\u01325\u0001\u0000\u0000\u0000\u0133\u0134\u0005\u001d\u0000"+ + "\u0000\u01347\u0001\u0000\u0000\u0000\u0135\u0136\u0003L&\u0000\u0136"+ + "9\u0001\u0000\u0000\u0000\u0137\u0138\u0003<\u001e\u0000\u0138;\u0001"+ + "\u0000\u0000\u0000\u0139\u013b\u0003>\u001f\u0000\u013a\u0139\u0001\u0000"+ + "\u0000\u0000\u013b\u013c\u0001\u0000\u0000\u0000\u013c\u013a\u0001\u0000"+ + "\u0000\u0000\u013c\u013d\u0001\u0000\u0000\u0000\u013d=\u0001\u0000\u0000"+ + "\u0000\u013e\u013f\u0007\u0000\u0000\u0000\u013f?\u0001\u0000\u0000\u0000"+ + "\u0140\u0141\u0003B!\u0000\u0141\u0143\u0005\'\u0000\u0000\u0142\u0144"+ + "\u0003F#\u0000\u0143\u0142\u0001\u0000\u0000\u0000\u0144\u0145\u0001\u0000"+ + "\u0000\u0000\u0145\u0143\u0001\u0000\u0000\u0000\u0145\u0146\u0001\u0000"+ + "\u0000\u0000\u0146A\u0001\u0000\u0000\u0000\u0147\u014b\u0005\u0017\u0000"+ + "\u0000\u0148\u014a\u0003D\"\u0000\u0149\u0148\u0001\u0000\u0000\u0000"+ + "\u014a\u014d\u0001\u0000\u0000\u0000\u014b\u0149\u0001\u0000\u0000\u0000"+ + "\u014b\u014c\u0001\u0000\u0000\u0000\u014c\u0157\u0001\u0000\u0000\u0000"+ + "\u014d\u014b\u0001\u0000\u0000\u0000\u014e\u0152\u0005\u0017\u0000\u0000"+ + "\u014f\u0151\u0003D\"\u0000\u0150\u014f\u0001\u0000\u0000\u0000\u0151"+ + "\u0154\u0001\u0000\u0000\u0000\u0152\u0150\u0001\u0000\u0000\u0000\u0152"+ + "\u0153\u0001\u0000\u0000\u0000\u0153\u0156\u0001\u0000\u0000\u0000\u0154"+ + "\u0152\u0001\u0000\u0000\u0000\u0155\u014e\u0001\u0000\u0000\u0000\u0156"+ + "\u0159\u0001\u0000\u0000\u0000\u0157\u0155\u0001\u0000\u0000\u0000\u0157"+ + "\u0158\u0001\u0000\u0000\u0000\u0158C\u0001\u0000\u0000\u0000\u0159\u0157"+ + "\u0001\u0000\u0000\u0000\u015a\u015b\u0007\u0000\u0000\u0000\u015bE\u0001"+ + "\u0000\u0000\u0000\u015c\u015d\u0003H$\u0000\u015d\u015e\u0003J%\u0000"+ + "\u015eG\u0001\u0000\u0000\u0000\u015f\u0161\u0007\u0001\u0000\u0000\u0160"+ + "\u015f\u0001\u0000\u0000\u0000\u0161\u0164\u0001\u0000\u0000\u0000\u0162"+ + "\u0160\u0001\u0000\u0000\u0000\u0162\u0163\u0001\u0000\u0000\u0000\u0163"+ + "I\u0001\u0000\u0000\u0000\u0164\u0162\u0001\u0000\u0000\u0000\u0165\u0166"+ + "\u0005%\u0000\u0000\u0166K\u0001\u0000\u0000\u0000\u0167\u0169\u0005\u0019"+ + "\u0000\u0000\u0168\u016a\u0003N\'\u0000\u0169\u0168\u0001\u0000\u0000"+ + "\u0000\u0169\u016a\u0001\u0000\u0000\u0000\u016a\u016b\u0001\u0000\u0000"+ + "\u0000\u016b\u016c\u0005\u001a\u0000\u0000\u016cM\u0001\u0000\u0000\u0000"+ + "\u016d\u0172\u0003P(\u0000\u016e\u016f\u0005\u001b\u0000\u0000\u016f\u0171"+ + "\u0003P(\u0000\u0170\u016e\u0001\u0000\u0000\u0000\u0171\u0174\u0001\u0000"+ + "\u0000\u0000\u0172\u0170\u0001\u0000\u0000\u0000\u0172\u0173\u0001\u0000"+ + "\u0000\u0000\u0173O\u0001\u0000\u0000\u0000\u0174\u0172\u0001\u0000\u0000"+ + "\u0000\u0175\u0176\u0005\u001f\u0000\u0000\u0176Q\u0001\u0000\u0000\u0000"+ + "\u0177\u0178\u0003\u008aE\u0000\u0178S\u0001\u0000\u0000\u0000\u0179\u017a"+ + "\u0005%\u0000\u0000\u017aU\u0001\u0000\u0000\u0000\u017b\u017d\u0003X"+ + ",\u0000\u017c\u017b\u0001\u0000\u0000\u0000\u017d\u017e\u0001\u0000\u0000"+ + "\u0000\u017e\u017c\u0001\u0000\u0000\u0000\u017e\u017f\u0001\u0000\u0000"+ + "\u0000\u017fW\u0001\u0000\u0000\u0000\u0180\u0181\u0003Z-\u0000\u0181"+ + "\u0182\u0005\u001c\u0000\u0000\u0182\u0183\u0003\\.\u0000\u0183\u0188"+ + "\u0001\u0000\u0000\u0000\u0184\u0185\u0003Z-\u0000\u0185\u0186\u0003^"+ + "/\u0000\u0186\u0188\u0001\u0000\u0000\u0000\u0187\u0180\u0001\u0000\u0000"+ + "\u0000\u0187\u0184\u0001\u0000\u0000\u0000\u0188Y\u0001\u0000\u0000\u0000"+ + "\u0189\u018a\u0007\u0002\u0000\u0000\u018a[\u0001\u0000\u0000\u0000\u018b"+ + "\u018c\u0007\u0002\u0000\u0000\u018c]\u0001\u0000\u0000\u0000\u018d\u018f"+ + "\u0003`0\u0000\u018e\u018d\u0001\u0000\u0000\u0000\u018f\u0190\u0001\u0000"+ + "\u0000\u0000\u0190\u018e\u0001\u0000\u0000\u0000\u0190\u0191\u0001\u0000"+ + "\u0000\u0000\u0191_\u0001\u0000\u0000\u0000\u0192\u0193\u0007\u0000\u0000"+ + "\u0000\u0193a\u0001\u0000\u0000\u0000\u0194\u0196\u0003d2\u0000\u0195"+ + "\u0194\u0001\u0000\u0000\u0000\u0196\u0197\u0001\u0000\u0000\u0000\u0197"+ + "\u0195\u0001\u0000\u0000\u0000\u0197\u0198\u0001\u0000\u0000\u0000\u0198"+ + "c\u0001\u0000\u0000\u0000\u0199\u019a\u0007\u0003\u0000\u0000\u019ae\u0001"+ + "\u0000\u0000\u0000\u019b\u019d\u0003h4\u0000\u019c\u019b\u0001\u0000\u0000"+ + "\u0000\u019d\u019e\u0001\u0000\u0000\u0000\u019e\u019c\u0001\u0000\u0000"+ + "\u0000\u019e\u019f\u0001\u0000\u0000\u0000\u019fg\u0001\u0000\u0000\u0000"+ + "\u01a0\u01a1\u0003j5\u0000\u01a1\u01a2\u0005\u001c\u0000\u0000\u01a2\u01a3"+ + "\u0003l6\u0000\u01a3\u01a8\u0001\u0000\u0000\u0000\u01a4\u01a5\u0003j"+ + "5\u0000\u01a5\u01a6\u0003n7\u0000\u01a6\u01a8\u0001\u0000\u0000\u0000"+ + "\u01a7\u01a0\u0001\u0000\u0000\u0000\u01a7\u01a4\u0001\u0000\u0000\u0000"+ + "\u01a8i\u0001\u0000\u0000\u0000\u01a9\u01aa\u0005%\u0000\u0000\u01aak"+ + "\u0001\u0000\u0000\u0000\u01ab\u01ac\u0003p8\u0000\u01acm\u0001\u0000"+ + "\u0000\u0000\u01ad\u01ae\u0003\u008aE\u0000\u01aeo\u0001\u0000\u0000\u0000"+ + "\u01af\u01b1\u0003r9\u0000\u01b0\u01af\u0001\u0000\u0000\u0000\u01b1\u01b2"+ + "\u0001\u0000\u0000\u0000\u01b2\u01b0\u0001\u0000\u0000\u0000\u01b2\u01b3"+ + "\u0001\u0000\u0000\u0000\u01b3q\u0001\u0000\u0000\u0000\u01b4\u01b5\u0007"+ + "\u0004\u0000\u0000\u01b5s\u0001\u0000\u0000\u0000\u01b6\u01b8\u0003v;"+ + "\u0000\u01b7\u01b6\u0001\u0000\u0000\u0000\u01b8\u01b9\u0001\u0000\u0000"+ + "\u0000\u01b9\u01b7\u0001\u0000\u0000\u0000\u01b9\u01ba\u0001\u0000\u0000"+ + "\u0000\u01bau\u0001\u0000\u0000\u0000\u01bb\u01bc\u0007\u0004\u0000\u0000"+ + "\u01bcw\u0001\u0000\u0000\u0000\u01bd\u01be\u0003z=\u0000\u01bey\u0001"+ + "\u0000\u0000\u0000\u01bf\u01c0\u0007\u0004\u0000\u0000\u01c0{\u0001\u0000"+ + "\u0000\u0000\u01c1\u01c2\u0003\u008aE\u0000\u01c2}\u0001\u0000\u0000\u0000"+ + "\u01c3\u01c5\u0003\u0080@\u0000\u01c4\u01c3\u0001\u0000\u0000\u0000\u01c5"+ + "\u01c6\u0001\u0000\u0000\u0000\u01c6\u01c4\u0001\u0000\u0000\u0000\u01c6"+ + "\u01c7\u0001\u0000\u0000\u0000\u01c7\u007f\u0001\u0000\u0000\u0000\u01c8"+ + "\u01c9\u0007\u0004\u0000\u0000\u01c9\u0081\u0001\u0000\u0000\u0000\u01ca"+ + "\u01cb\u0003\u008aE\u0000\u01cb\u0083\u0001\u0000\u0000\u0000\u01cc\u01cd"+ + "\u0005%\u0000\u0000\u01cd\u0085\u0001\u0000\u0000\u0000\u01ce\u01cf\u0003"+ + "\u008aE\u0000\u01cf\u0087\u0001\u0000\u0000\u0000\u01d0\u01d1\u0005%\u0000"+ + "\u0000\u01d1\u0089\u0001\u0000\u0000\u0000\u01d2\u01d4\u0003\u008cF\u0000"+ + "\u01d3\u01d2\u0001\u0000\u0000\u0000\u01d4\u01d5\u0001\u0000\u0000\u0000"+ + "\u01d5\u01d3\u0001\u0000\u0000\u0000\u01d5\u01d6\u0001\u0000\u0000\u0000"+ + "\u01d6\u008b\u0001\u0000\u0000\u0000\u01d7\u01d8\u0007\u0000\u0000\u0000"+ + "\u01d8\u008d\u0001\u0000\u0000\u0000)\u0091\u0098\u00a1\u00a8\u00bc\u00c0"+ + "\u00c4\u00c9\u00cd\u00d2\u00d7\u00e4\u00eb\u00ef\u00f6\u00fb\u0100\u010c"+ + "\u0118\u011d\u011f\u0124\u0131\u013c\u0145\u014b\u0152\u0157\u0162\u0169"+ + "\u0172\u017e\u0187\u0190\u0197\u019e\u01a7\u01b2\u01b9\u01c6\u01d5"; + public static final ATN _ATN = + new ATNDeserializer().deserialize(_serializedATN.toCharArray()); + static { + _decisionToDFA = new DFA[_ATN.getNumberOfDecisions()]; + for (int i = 0; i < _ATN.getNumberOfDecisions(); i++) { + _decisionToDFA[i] = new DFA(_ATN.getDecisionState(i), i); + } + } +} \ No newline at end of file diff --git a/rewrite-docker/src/main/java/org/openrewrite/docker/internal/grammar/DockerParser.tokens b/rewrite-docker/src/main/java/org/openrewrite/docker/internal/grammar/DockerParser.tokens new file mode 100644 index 0000000000..8f8b4bd73c --- /dev/null +++ b/rewrite-docker/src/main/java/org/openrewrite/docker/internal/grammar/DockerParser.tokens @@ -0,0 +1,71 @@ +PARSER_DIRECTIVE=1 +COMMENT=2 +FROM=3 +RUN=4 +CMD=5 +NONE=6 +LABEL=7 +EXPOSE=8 +ENV=9 +ADD=10 +COPY=11 +ENTRYPOINT=12 +VOLUME=13 +USER=14 +WORKDIR=15 +ARG=16 +ONBUILD=17 +STOPSIGNAL=18 +HEALTHCHECK=19 +SHELL=20 +MAINTAINER=21 +AS=22 +HEREDOC_START=23 +LINE_CONTINUATION=24 +LBRACKET=25 +RBRACKET=26 +COMMA=27 +EQUALS=28 +FLAG=29 +DASH_DASH=30 +DOUBLE_QUOTED_STRING=31 +SINGLE_QUOTED_STRING=32 +ENV_VAR=33 +SPECIAL_VAR=34 +COMMAND_SUBST=35 +BACKTICK_SUBST=36 +UNQUOTED_TEXT=37 +WS=38 +NEWLINE=39 +HP_LINE_CONTINUATION=40 +HP_WS=41 +HP_COMMENT=42 +HP_LINE_COMMENT=43 +HEREDOC_CONTENT=44 +H_NEWLINE=45 +'FROM'=3 +'RUN'=4 +'CMD'=5 +'NONE'=6 +'LABEL'=7 +'EXPOSE'=8 +'ENV'=9 +'ADD'=10 +'COPY'=11 +'ENTRYPOINT'=12 +'VOLUME'=13 +'USER'=14 +'WORKDIR'=15 +'ARG'=16 +'ONBUILD'=17 +'STOPSIGNAL'=18 +'HEALTHCHECK'=19 +'SHELL'=20 +'MAINTAINER'=21 +'AS'=22 +'['=25 +']'=26 +','=27 +'='=28 +'--'=30 +'\n'=45 diff --git a/rewrite-docker/src/main/java/org/openrewrite/docker/internal/grammar/DockerParserBaseListener.java b/rewrite-docker/src/main/java/org/openrewrite/docker/internal/grammar/DockerParserBaseListener.java new file mode 100644 index 0000000000..88df4db91b --- /dev/null +++ b/rewrite-docker/src/main/java/org/openrewrite/docker/internal/grammar/DockerParserBaseListener.java @@ -0,0 +1,907 @@ +/* + * Copyright 2026 the original author or authors. + *

+ * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * https://www.apache.org/licenses/LICENSE-2.0 + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +// Generated from /home/tim/Documents/workspace/openrewrite/rewrite/rewrite-docker/src/main/antlr/DockerParser.g4 by ANTLR 4.13.2 +package org.openrewrite.docker.internal.grammar; + +import org.antlr.v4.runtime.ParserRuleContext; +import org.antlr.v4.runtime.tree.ErrorNode; +import org.antlr.v4.runtime.tree.TerminalNode; + +/** + * This class provides an empty implementation of {@link DockerParserListener}, + * which can be extended to create a listener which only needs to handle a subset + * of the available methods. + */ +@SuppressWarnings("CheckReturnValue") +public class DockerParserBaseListener implements DockerParserListener { + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override public void enterDockerfile(DockerParser.DockerfileContext ctx) { } + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override public void exitDockerfile(DockerParser.DockerfileContext ctx) { } + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override public void enterParserDirective(DockerParser.ParserDirectiveContext ctx) { } + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override public void exitParserDirective(DockerParser.ParserDirectiveContext ctx) { } + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override public void enterGlobalArgs(DockerParser.GlobalArgsContext ctx) { } + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override public void exitGlobalArgs(DockerParser.GlobalArgsContext ctx) { } + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override public void enterStage(DockerParser.StageContext ctx) { } + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override public void exitStage(DockerParser.StageContext ctx) { } + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override public void enterStageInstruction(DockerParser.StageInstructionContext ctx) { } + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override public void exitStageInstruction(DockerParser.StageInstructionContext ctx) { } + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override public void enterInstruction(DockerParser.InstructionContext ctx) { } + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override public void exitInstruction(DockerParser.InstructionContext ctx) { } + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override public void enterFromInstruction(DockerParser.FromInstructionContext ctx) { } + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override public void exitFromInstruction(DockerParser.FromInstructionContext ctx) { } + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override public void enterRunInstruction(DockerParser.RunInstructionContext ctx) { } + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override public void exitRunInstruction(DockerParser.RunInstructionContext ctx) { } + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override public void enterCmdInstruction(DockerParser.CmdInstructionContext ctx) { } + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override public void exitCmdInstruction(DockerParser.CmdInstructionContext ctx) { } + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override public void enterLabelInstruction(DockerParser.LabelInstructionContext ctx) { } + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override public void exitLabelInstruction(DockerParser.LabelInstructionContext ctx) { } + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override public void enterExposeInstruction(DockerParser.ExposeInstructionContext ctx) { } + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override public void exitExposeInstruction(DockerParser.ExposeInstructionContext ctx) { } + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override public void enterEnvInstruction(DockerParser.EnvInstructionContext ctx) { } + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override public void exitEnvInstruction(DockerParser.EnvInstructionContext ctx) { } + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override public void enterAddInstruction(DockerParser.AddInstructionContext ctx) { } + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override public void exitAddInstruction(DockerParser.AddInstructionContext ctx) { } + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override public void enterCopyInstruction(DockerParser.CopyInstructionContext ctx) { } + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override public void exitCopyInstruction(DockerParser.CopyInstructionContext ctx) { } + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override public void enterEntrypointInstruction(DockerParser.EntrypointInstructionContext ctx) { } + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override public void exitEntrypointInstruction(DockerParser.EntrypointInstructionContext ctx) { } + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override public void enterVolumeInstruction(DockerParser.VolumeInstructionContext ctx) { } + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override public void exitVolumeInstruction(DockerParser.VolumeInstructionContext ctx) { } + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override public void enterUserInstruction(DockerParser.UserInstructionContext ctx) { } + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override public void exitUserInstruction(DockerParser.UserInstructionContext ctx) { } + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override public void enterWorkdirInstruction(DockerParser.WorkdirInstructionContext ctx) { } + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override public void exitWorkdirInstruction(DockerParser.WorkdirInstructionContext ctx) { } + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override public void enterArgInstruction(DockerParser.ArgInstructionContext ctx) { } + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override public void exitArgInstruction(DockerParser.ArgInstructionContext ctx) { } + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override public void enterOnbuildInstruction(DockerParser.OnbuildInstructionContext ctx) { } + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override public void exitOnbuildInstruction(DockerParser.OnbuildInstructionContext ctx) { } + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override public void enterStopsignalInstruction(DockerParser.StopsignalInstructionContext ctx) { } + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override public void exitStopsignalInstruction(DockerParser.StopsignalInstructionContext ctx) { } + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override public void enterHealthcheckInstruction(DockerParser.HealthcheckInstructionContext ctx) { } + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override public void exitHealthcheckInstruction(DockerParser.HealthcheckInstructionContext ctx) { } + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override public void enterHealthcheckOptions(DockerParser.HealthcheckOptionsContext ctx) { } + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override public void exitHealthcheckOptions(DockerParser.HealthcheckOptionsContext ctx) { } + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override public void enterHealthcheckOption(DockerParser.HealthcheckOptionContext ctx) { } + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override public void exitHealthcheckOption(DockerParser.HealthcheckOptionContext ctx) { } + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override public void enterShellInstruction(DockerParser.ShellInstructionContext ctx) { } + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override public void exitShellInstruction(DockerParser.ShellInstructionContext ctx) { } + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override public void enterMaintainerInstruction(DockerParser.MaintainerInstructionContext ctx) { } + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override public void exitMaintainerInstruction(DockerParser.MaintainerInstructionContext ctx) { } + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override public void enterFlags(DockerParser.FlagsContext ctx) { } + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override public void exitFlags(DockerParser.FlagsContext ctx) { } + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override public void enterFlag(DockerParser.FlagContext ctx) { } + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override public void exitFlag(DockerParser.FlagContext ctx) { } + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override public void enterExecForm(DockerParser.ExecFormContext ctx) { } + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override public void exitExecForm(DockerParser.ExecFormContext ctx) { } + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override public void enterShellForm(DockerParser.ShellFormContext ctx) { } + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override public void exitShellForm(DockerParser.ShellFormContext ctx) { } + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override public void enterShellFormText(DockerParser.ShellFormTextContext ctx) { } + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override public void exitShellFormText(DockerParser.ShellFormTextContext ctx) { } + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override public void enterShellFormTextElement(DockerParser.ShellFormTextElementContext ctx) { } + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override public void exitShellFormTextElement(DockerParser.ShellFormTextElementContext ctx) { } + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override public void enterHeredoc(DockerParser.HeredocContext ctx) { } + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override public void exitHeredoc(DockerParser.HeredocContext ctx) { } + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override public void enterHeredocPreamble(DockerParser.HeredocPreambleContext ctx) { } + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override public void exitHeredocPreamble(DockerParser.HeredocPreambleContext ctx) { } + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override public void enterPreambleElement(DockerParser.PreambleElementContext ctx) { } + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override public void exitPreambleElement(DockerParser.PreambleElementContext ctx) { } + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override public void enterHeredocBody(DockerParser.HeredocBodyContext ctx) { } + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override public void exitHeredocBody(DockerParser.HeredocBodyContext ctx) { } + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override public void enterHeredocContent(DockerParser.HeredocContentContext ctx) { } + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override public void exitHeredocContent(DockerParser.HeredocContentContext ctx) { } + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override public void enterHeredocEnd(DockerParser.HeredocEndContext ctx) { } + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override public void exitHeredocEnd(DockerParser.HeredocEndContext ctx) { } + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override public void enterJsonArray(DockerParser.JsonArrayContext ctx) { } + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override public void exitJsonArray(DockerParser.JsonArrayContext ctx) { } + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override public void enterJsonArrayElements(DockerParser.JsonArrayElementsContext ctx) { } + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override public void exitJsonArrayElements(DockerParser.JsonArrayElementsContext ctx) { } + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override public void enterJsonString(DockerParser.JsonStringContext ctx) { } + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override public void exitJsonString(DockerParser.JsonStringContext ctx) { } + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override public void enterImageName(DockerParser.ImageNameContext ctx) { } + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override public void exitImageName(DockerParser.ImageNameContext ctx) { } + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override public void enterStageName(DockerParser.StageNameContext ctx) { } + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override public void exitStageName(DockerParser.StageNameContext ctx) { } + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override public void enterLabelPairs(DockerParser.LabelPairsContext ctx) { } + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override public void exitLabelPairs(DockerParser.LabelPairsContext ctx) { } + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override public void enterLabelPair(DockerParser.LabelPairContext ctx) { } + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override public void exitLabelPair(DockerParser.LabelPairContext ctx) { } + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override public void enterLabelKey(DockerParser.LabelKeyContext ctx) { } + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override public void exitLabelKey(DockerParser.LabelKeyContext ctx) { } + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override public void enterLabelValue(DockerParser.LabelValueContext ctx) { } + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override public void exitLabelValue(DockerParser.LabelValueContext ctx) { } + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override public void enterLabelOldValue(DockerParser.LabelOldValueContext ctx) { } + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override public void exitLabelOldValue(DockerParser.LabelOldValueContext ctx) { } + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override public void enterLabelOldValueElement(DockerParser.LabelOldValueElementContext ctx) { } + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override public void exitLabelOldValueElement(DockerParser.LabelOldValueElementContext ctx) { } + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override public void enterPortList(DockerParser.PortListContext ctx) { } + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override public void exitPortList(DockerParser.PortListContext ctx) { } + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override public void enterPort(DockerParser.PortContext ctx) { } + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override public void exitPort(DockerParser.PortContext ctx) { } + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override public void enterEnvPairs(DockerParser.EnvPairsContext ctx) { } + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override public void exitEnvPairs(DockerParser.EnvPairsContext ctx) { } + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override public void enterEnvPair(DockerParser.EnvPairContext ctx) { } + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override public void exitEnvPair(DockerParser.EnvPairContext ctx) { } + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override public void enterEnvKey(DockerParser.EnvKeyContext ctx) { } + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override public void exitEnvKey(DockerParser.EnvKeyContext ctx) { } + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override public void enterEnvValueEquals(DockerParser.EnvValueEqualsContext ctx) { } + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override public void exitEnvValueEquals(DockerParser.EnvValueEqualsContext ctx) { } + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override public void enterEnvValueSpace(DockerParser.EnvValueSpaceContext ctx) { } + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override public void exitEnvValueSpace(DockerParser.EnvValueSpaceContext ctx) { } + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override public void enterEnvTextEquals(DockerParser.EnvTextEqualsContext ctx) { } + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override public void exitEnvTextEquals(DockerParser.EnvTextEqualsContext ctx) { } + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override public void enterEnvTextElementEquals(DockerParser.EnvTextElementEqualsContext ctx) { } + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override public void exitEnvTextElementEquals(DockerParser.EnvTextElementEqualsContext ctx) { } + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override public void enterSourceList(DockerParser.SourceListContext ctx) { } + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override public void exitSourceList(DockerParser.SourceListContext ctx) { } + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override public void enterSourcePath(DockerParser.SourcePathContext ctx) { } + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override public void exitSourcePath(DockerParser.SourcePathContext ctx) { } + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override public void enterDestination(DockerParser.DestinationContext ctx) { } + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override public void exitDestination(DockerParser.DestinationContext ctx) { } + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override public void enterDestinationPath(DockerParser.DestinationPathContext ctx) { } + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override public void exitDestinationPath(DockerParser.DestinationPathContext ctx) { } + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override public void enterPath(DockerParser.PathContext ctx) { } + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override public void exitPath(DockerParser.PathContext ctx) { } + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override public void enterPathList(DockerParser.PathListContext ctx) { } + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override public void exitPathList(DockerParser.PathListContext ctx) { } + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override public void enterVolumePath(DockerParser.VolumePathContext ctx) { } + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override public void exitVolumePath(DockerParser.VolumePathContext ctx) { } + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override public void enterUserSpec(DockerParser.UserSpecContext ctx) { } + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override public void exitUserSpec(DockerParser.UserSpecContext ctx) { } + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override public void enterArgName(DockerParser.ArgNameContext ctx) { } + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override public void exitArgName(DockerParser.ArgNameContext ctx) { } + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override public void enterArgValue(DockerParser.ArgValueContext ctx) { } + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override public void exitArgValue(DockerParser.ArgValueContext ctx) { } + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override public void enterSignal(DockerParser.SignalContext ctx) { } + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override public void exitSignal(DockerParser.SignalContext ctx) { } + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override public void enterText(DockerParser.TextContext ctx) { } + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override public void exitText(DockerParser.TextContext ctx) { } + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override public void enterTextElement(DockerParser.TextElementContext ctx) { } + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override public void exitTextElement(DockerParser.TextElementContext ctx) { } + + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override public void enterEveryRule(ParserRuleContext ctx) { } + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override public void exitEveryRule(ParserRuleContext ctx) { } + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override public void visitTerminal(TerminalNode node) { } + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override public void visitErrorNode(ErrorNode node) { } +} \ No newline at end of file diff --git a/rewrite-docker/src/main/java/org/openrewrite/docker/internal/grammar/DockerParserBaseVisitor.java b/rewrite-docker/src/main/java/org/openrewrite/docker/internal/grammar/DockerParserBaseVisitor.java new file mode 100644 index 0000000000..51c7218256 --- /dev/null +++ b/rewrite-docker/src/main/java/org/openrewrite/docker/internal/grammar/DockerParserBaseVisitor.java @@ -0,0 +1,527 @@ +/* + * Copyright 2026 the original author or authors. + *

+ * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * https://www.apache.org/licenses/LICENSE-2.0 + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +// Generated from /home/tim/Documents/workspace/openrewrite/rewrite/rewrite-docker/src/main/antlr/DockerParser.g4 by ANTLR 4.13.2 +package org.openrewrite.docker.internal.grammar; +import org.antlr.v4.runtime.tree.AbstractParseTreeVisitor; + +/** + * This class provides an empty implementation of {@link DockerParserVisitor}, + * which can be extended to create a visitor which only needs to handle a subset + * of the available methods. + * + * @param The return type of the visit operation. Use {@link Void} for + * operations with no return type. + */ +@SuppressWarnings("CheckReturnValue") +public class DockerParserBaseVisitor extends AbstractParseTreeVisitor implements DockerParserVisitor { + /** + * {@inheritDoc} + * + *

The default implementation returns the result of calling + * {@link #visitChildren} on {@code ctx}.

+ */ + @Override public T visitDockerfile(DockerParser.DockerfileContext ctx) { return visitChildren(ctx); } + /** + * {@inheritDoc} + * + *

The default implementation returns the result of calling + * {@link #visitChildren} on {@code ctx}.

+ */ + @Override public T visitParserDirective(DockerParser.ParserDirectiveContext ctx) { return visitChildren(ctx); } + /** + * {@inheritDoc} + * + *

The default implementation returns the result of calling + * {@link #visitChildren} on {@code ctx}.

+ */ + @Override public T visitGlobalArgs(DockerParser.GlobalArgsContext ctx) { return visitChildren(ctx); } + /** + * {@inheritDoc} + * + *

The default implementation returns the result of calling + * {@link #visitChildren} on {@code ctx}.

+ */ + @Override public T visitStage(DockerParser.StageContext ctx) { return visitChildren(ctx); } + /** + * {@inheritDoc} + * + *

The default implementation returns the result of calling + * {@link #visitChildren} on {@code ctx}.

+ */ + @Override public T visitStageInstruction(DockerParser.StageInstructionContext ctx) { return visitChildren(ctx); } + /** + * {@inheritDoc} + * + *

The default implementation returns the result of calling + * {@link #visitChildren} on {@code ctx}.

+ */ + @Override public T visitInstruction(DockerParser.InstructionContext ctx) { return visitChildren(ctx); } + /** + * {@inheritDoc} + * + *

The default implementation returns the result of calling + * {@link #visitChildren} on {@code ctx}.

+ */ + @Override public T visitFromInstruction(DockerParser.FromInstructionContext ctx) { return visitChildren(ctx); } + /** + * {@inheritDoc} + * + *

The default implementation returns the result of calling + * {@link #visitChildren} on {@code ctx}.

+ */ + @Override public T visitRunInstruction(DockerParser.RunInstructionContext ctx) { return visitChildren(ctx); } + /** + * {@inheritDoc} + * + *

The default implementation returns the result of calling + * {@link #visitChildren} on {@code ctx}.

+ */ + @Override public T visitCmdInstruction(DockerParser.CmdInstructionContext ctx) { return visitChildren(ctx); } + /** + * {@inheritDoc} + * + *

The default implementation returns the result of calling + * {@link #visitChildren} on {@code ctx}.

+ */ + @Override public T visitLabelInstruction(DockerParser.LabelInstructionContext ctx) { return visitChildren(ctx); } + /** + * {@inheritDoc} + * + *

The default implementation returns the result of calling + * {@link #visitChildren} on {@code ctx}.

+ */ + @Override public T visitExposeInstruction(DockerParser.ExposeInstructionContext ctx) { return visitChildren(ctx); } + /** + * {@inheritDoc} + * + *

The default implementation returns the result of calling + * {@link #visitChildren} on {@code ctx}.

+ */ + @Override public T visitEnvInstruction(DockerParser.EnvInstructionContext ctx) { return visitChildren(ctx); } + /** + * {@inheritDoc} + * + *

The default implementation returns the result of calling + * {@link #visitChildren} on {@code ctx}.

+ */ + @Override public T visitAddInstruction(DockerParser.AddInstructionContext ctx) { return visitChildren(ctx); } + /** + * {@inheritDoc} + * + *

The default implementation returns the result of calling + * {@link #visitChildren} on {@code ctx}.

+ */ + @Override public T visitCopyInstruction(DockerParser.CopyInstructionContext ctx) { return visitChildren(ctx); } + /** + * {@inheritDoc} + * + *

The default implementation returns the result of calling + * {@link #visitChildren} on {@code ctx}.

+ */ + @Override public T visitEntrypointInstruction(DockerParser.EntrypointInstructionContext ctx) { return visitChildren(ctx); } + /** + * {@inheritDoc} + * + *

The default implementation returns the result of calling + * {@link #visitChildren} on {@code ctx}.

+ */ + @Override public T visitVolumeInstruction(DockerParser.VolumeInstructionContext ctx) { return visitChildren(ctx); } + /** + * {@inheritDoc} + * + *

The default implementation returns the result of calling + * {@link #visitChildren} on {@code ctx}.

+ */ + @Override public T visitUserInstruction(DockerParser.UserInstructionContext ctx) { return visitChildren(ctx); } + /** + * {@inheritDoc} + * + *

The default implementation returns the result of calling + * {@link #visitChildren} on {@code ctx}.

+ */ + @Override public T visitWorkdirInstruction(DockerParser.WorkdirInstructionContext ctx) { return visitChildren(ctx); } + /** + * {@inheritDoc} + * + *

The default implementation returns the result of calling + * {@link #visitChildren} on {@code ctx}.

+ */ + @Override public T visitArgInstruction(DockerParser.ArgInstructionContext ctx) { return visitChildren(ctx); } + /** + * {@inheritDoc} + * + *

The default implementation returns the result of calling + * {@link #visitChildren} on {@code ctx}.

+ */ + @Override public T visitOnbuildInstruction(DockerParser.OnbuildInstructionContext ctx) { return visitChildren(ctx); } + /** + * {@inheritDoc} + * + *

The default implementation returns the result of calling + * {@link #visitChildren} on {@code ctx}.

+ */ + @Override public T visitStopsignalInstruction(DockerParser.StopsignalInstructionContext ctx) { return visitChildren(ctx); } + /** + * {@inheritDoc} + * + *

The default implementation returns the result of calling + * {@link #visitChildren} on {@code ctx}.

+ */ + @Override public T visitHealthcheckInstruction(DockerParser.HealthcheckInstructionContext ctx) { return visitChildren(ctx); } + /** + * {@inheritDoc} + * + *

The default implementation returns the result of calling + * {@link #visitChildren} on {@code ctx}.

+ */ + @Override public T visitHealthcheckOptions(DockerParser.HealthcheckOptionsContext ctx) { return visitChildren(ctx); } + /** + * {@inheritDoc} + * + *

The default implementation returns the result of calling + * {@link #visitChildren} on {@code ctx}.

+ */ + @Override public T visitHealthcheckOption(DockerParser.HealthcheckOptionContext ctx) { return visitChildren(ctx); } + /** + * {@inheritDoc} + * + *

The default implementation returns the result of calling + * {@link #visitChildren} on {@code ctx}.

+ */ + @Override public T visitShellInstruction(DockerParser.ShellInstructionContext ctx) { return visitChildren(ctx); } + /** + * {@inheritDoc} + * + *

The default implementation returns the result of calling + * {@link #visitChildren} on {@code ctx}.

+ */ + @Override public T visitMaintainerInstruction(DockerParser.MaintainerInstructionContext ctx) { return visitChildren(ctx); } + /** + * {@inheritDoc} + * + *

The default implementation returns the result of calling + * {@link #visitChildren} on {@code ctx}.

+ */ + @Override public T visitFlags(DockerParser.FlagsContext ctx) { return visitChildren(ctx); } + /** + * {@inheritDoc} + * + *

The default implementation returns the result of calling + * {@link #visitChildren} on {@code ctx}.

+ */ + @Override public T visitFlag(DockerParser.FlagContext ctx) { return visitChildren(ctx); } + /** + * {@inheritDoc} + * + *

The default implementation returns the result of calling + * {@link #visitChildren} on {@code ctx}.

+ */ + @Override public T visitExecForm(DockerParser.ExecFormContext ctx) { return visitChildren(ctx); } + /** + * {@inheritDoc} + * + *

The default implementation returns the result of calling + * {@link #visitChildren} on {@code ctx}.

+ */ + @Override public T visitShellForm(DockerParser.ShellFormContext ctx) { return visitChildren(ctx); } + /** + * {@inheritDoc} + * + *

The default implementation returns the result of calling + * {@link #visitChildren} on {@code ctx}.

+ */ + @Override public T visitShellFormText(DockerParser.ShellFormTextContext ctx) { return visitChildren(ctx); } + /** + * {@inheritDoc} + * + *

The default implementation returns the result of calling + * {@link #visitChildren} on {@code ctx}.

+ */ + @Override public T visitShellFormTextElement(DockerParser.ShellFormTextElementContext ctx) { return visitChildren(ctx); } + /** + * {@inheritDoc} + * + *

The default implementation returns the result of calling + * {@link #visitChildren} on {@code ctx}.

+ */ + @Override public T visitHeredoc(DockerParser.HeredocContext ctx) { return visitChildren(ctx); } + /** + * {@inheritDoc} + * + *

The default implementation returns the result of calling + * {@link #visitChildren} on {@code ctx}.

+ */ + @Override public T visitHeredocPreamble(DockerParser.HeredocPreambleContext ctx) { return visitChildren(ctx); } + /** + * {@inheritDoc} + * + *

The default implementation returns the result of calling + * {@link #visitChildren} on {@code ctx}.

+ */ + @Override public T visitPreambleElement(DockerParser.PreambleElementContext ctx) { return visitChildren(ctx); } + /** + * {@inheritDoc} + * + *

The default implementation returns the result of calling + * {@link #visitChildren} on {@code ctx}.

+ */ + @Override public T visitHeredocBody(DockerParser.HeredocBodyContext ctx) { return visitChildren(ctx); } + /** + * {@inheritDoc} + * + *

The default implementation returns the result of calling + * {@link #visitChildren} on {@code ctx}.

+ */ + @Override public T visitHeredocContent(DockerParser.HeredocContentContext ctx) { return visitChildren(ctx); } + /** + * {@inheritDoc} + * + *

The default implementation returns the result of calling + * {@link #visitChildren} on {@code ctx}.

+ */ + @Override public T visitHeredocEnd(DockerParser.HeredocEndContext ctx) { return visitChildren(ctx); } + /** + * {@inheritDoc} + * + *

The default implementation returns the result of calling + * {@link #visitChildren} on {@code ctx}.

+ */ + @Override public T visitJsonArray(DockerParser.JsonArrayContext ctx) { return visitChildren(ctx); } + /** + * {@inheritDoc} + * + *

The default implementation returns the result of calling + * {@link #visitChildren} on {@code ctx}.

+ */ + @Override public T visitJsonArrayElements(DockerParser.JsonArrayElementsContext ctx) { return visitChildren(ctx); } + /** + * {@inheritDoc} + * + *

The default implementation returns the result of calling + * {@link #visitChildren} on {@code ctx}.

+ */ + @Override public T visitJsonString(DockerParser.JsonStringContext ctx) { return visitChildren(ctx); } + /** + * {@inheritDoc} + * + *

The default implementation returns the result of calling + * {@link #visitChildren} on {@code ctx}.

+ */ + @Override public T visitImageName(DockerParser.ImageNameContext ctx) { return visitChildren(ctx); } + /** + * {@inheritDoc} + * + *

The default implementation returns the result of calling + * {@link #visitChildren} on {@code ctx}.

+ */ + @Override public T visitStageName(DockerParser.StageNameContext ctx) { return visitChildren(ctx); } + /** + * {@inheritDoc} + * + *

The default implementation returns the result of calling + * {@link #visitChildren} on {@code ctx}.

+ */ + @Override public T visitLabelPairs(DockerParser.LabelPairsContext ctx) { return visitChildren(ctx); } + /** + * {@inheritDoc} + * + *

The default implementation returns the result of calling + * {@link #visitChildren} on {@code ctx}.

+ */ + @Override public T visitLabelPair(DockerParser.LabelPairContext ctx) { return visitChildren(ctx); } + /** + * {@inheritDoc} + * + *

The default implementation returns the result of calling + * {@link #visitChildren} on {@code ctx}.

+ */ + @Override public T visitLabelKey(DockerParser.LabelKeyContext ctx) { return visitChildren(ctx); } + /** + * {@inheritDoc} + * + *

The default implementation returns the result of calling + * {@link #visitChildren} on {@code ctx}.

+ */ + @Override public T visitLabelValue(DockerParser.LabelValueContext ctx) { return visitChildren(ctx); } + /** + * {@inheritDoc} + * + *

The default implementation returns the result of calling + * {@link #visitChildren} on {@code ctx}.

+ */ + @Override public T visitLabelOldValue(DockerParser.LabelOldValueContext ctx) { return visitChildren(ctx); } + /** + * {@inheritDoc} + * + *

The default implementation returns the result of calling + * {@link #visitChildren} on {@code ctx}.

+ */ + @Override public T visitLabelOldValueElement(DockerParser.LabelOldValueElementContext ctx) { return visitChildren(ctx); } + /** + * {@inheritDoc} + * + *

The default implementation returns the result of calling + * {@link #visitChildren} on {@code ctx}.

+ */ + @Override public T visitPortList(DockerParser.PortListContext ctx) { return visitChildren(ctx); } + /** + * {@inheritDoc} + * + *

The default implementation returns the result of calling + * {@link #visitChildren} on {@code ctx}.

+ */ + @Override public T visitPort(DockerParser.PortContext ctx) { return visitChildren(ctx); } + /** + * {@inheritDoc} + * + *

The default implementation returns the result of calling + * {@link #visitChildren} on {@code ctx}.

+ */ + @Override public T visitEnvPairs(DockerParser.EnvPairsContext ctx) { return visitChildren(ctx); } + /** + * {@inheritDoc} + * + *

The default implementation returns the result of calling + * {@link #visitChildren} on {@code ctx}.

+ */ + @Override public T visitEnvPair(DockerParser.EnvPairContext ctx) { return visitChildren(ctx); } + /** + * {@inheritDoc} + * + *

The default implementation returns the result of calling + * {@link #visitChildren} on {@code ctx}.

+ */ + @Override public T visitEnvKey(DockerParser.EnvKeyContext ctx) { return visitChildren(ctx); } + /** + * {@inheritDoc} + * + *

The default implementation returns the result of calling + * {@link #visitChildren} on {@code ctx}.

+ */ + @Override public T visitEnvValueEquals(DockerParser.EnvValueEqualsContext ctx) { return visitChildren(ctx); } + /** + * {@inheritDoc} + * + *

The default implementation returns the result of calling + * {@link #visitChildren} on {@code ctx}.

+ */ + @Override public T visitEnvValueSpace(DockerParser.EnvValueSpaceContext ctx) { return visitChildren(ctx); } + /** + * {@inheritDoc} + * + *

The default implementation returns the result of calling + * {@link #visitChildren} on {@code ctx}.

+ */ + @Override public T visitEnvTextEquals(DockerParser.EnvTextEqualsContext ctx) { return visitChildren(ctx); } + /** + * {@inheritDoc} + * + *

The default implementation returns the result of calling + * {@link #visitChildren} on {@code ctx}.

+ */ + @Override public T visitEnvTextElementEquals(DockerParser.EnvTextElementEqualsContext ctx) { return visitChildren(ctx); } + /** + * {@inheritDoc} + * + *

The default implementation returns the result of calling + * {@link #visitChildren} on {@code ctx}.

+ */ + @Override public T visitSourceList(DockerParser.SourceListContext ctx) { return visitChildren(ctx); } + /** + * {@inheritDoc} + * + *

The default implementation returns the result of calling + * {@link #visitChildren} on {@code ctx}.

+ */ + @Override public T visitSourcePath(DockerParser.SourcePathContext ctx) { return visitChildren(ctx); } + /** + * {@inheritDoc} + * + *

The default implementation returns the result of calling + * {@link #visitChildren} on {@code ctx}.

+ */ + @Override public T visitDestination(DockerParser.DestinationContext ctx) { return visitChildren(ctx); } + /** + * {@inheritDoc} + * + *

The default implementation returns the result of calling + * {@link #visitChildren} on {@code ctx}.

+ */ + @Override public T visitDestinationPath(DockerParser.DestinationPathContext ctx) { return visitChildren(ctx); } + /** + * {@inheritDoc} + * + *

The default implementation returns the result of calling + * {@link #visitChildren} on {@code ctx}.

+ */ + @Override public T visitPath(DockerParser.PathContext ctx) { return visitChildren(ctx); } + /** + * {@inheritDoc} + * + *

The default implementation returns the result of calling + * {@link #visitChildren} on {@code ctx}.

+ */ + @Override public T visitPathList(DockerParser.PathListContext ctx) { return visitChildren(ctx); } + /** + * {@inheritDoc} + * + *

The default implementation returns the result of calling + * {@link #visitChildren} on {@code ctx}.

+ */ + @Override public T visitVolumePath(DockerParser.VolumePathContext ctx) { return visitChildren(ctx); } + /** + * {@inheritDoc} + * + *

The default implementation returns the result of calling + * {@link #visitChildren} on {@code ctx}.

+ */ + @Override public T visitUserSpec(DockerParser.UserSpecContext ctx) { return visitChildren(ctx); } + /** + * {@inheritDoc} + * + *

The default implementation returns the result of calling + * {@link #visitChildren} on {@code ctx}.

+ */ + @Override public T visitArgName(DockerParser.ArgNameContext ctx) { return visitChildren(ctx); } + /** + * {@inheritDoc} + * + *

The default implementation returns the result of calling + * {@link #visitChildren} on {@code ctx}.

+ */ + @Override public T visitArgValue(DockerParser.ArgValueContext ctx) { return visitChildren(ctx); } + /** + * {@inheritDoc} + * + *

The default implementation returns the result of calling + * {@link #visitChildren} on {@code ctx}.

+ */ + @Override public T visitSignal(DockerParser.SignalContext ctx) { return visitChildren(ctx); } + /** + * {@inheritDoc} + * + *

The default implementation returns the result of calling + * {@link #visitChildren} on {@code ctx}.

+ */ + @Override public T visitText(DockerParser.TextContext ctx) { return visitChildren(ctx); } + /** + * {@inheritDoc} + * + *

The default implementation returns the result of calling + * {@link #visitChildren} on {@code ctx}.

+ */ + @Override public T visitTextElement(DockerParser.TextElementContext ctx) { return visitChildren(ctx); } +} \ No newline at end of file diff --git a/rewrite-docker/src/main/java/org/openrewrite/docker/internal/grammar/DockerParserListener.java b/rewrite-docker/src/main/java/org/openrewrite/docker/internal/grammar/DockerParserListener.java new file mode 100644 index 0000000000..0006918506 --- /dev/null +++ b/rewrite-docker/src/main/java/org/openrewrite/docker/internal/grammar/DockerParserListener.java @@ -0,0 +1,735 @@ +/* + * Copyright 2026 the original author or authors. + *

+ * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * https://www.apache.org/licenses/LICENSE-2.0 + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +// Generated from /home/tim/Documents/workspace/openrewrite/rewrite/rewrite-docker/src/main/antlr/DockerParser.g4 by ANTLR 4.13.2 +package org.openrewrite.docker.internal.grammar; +import org.antlr.v4.runtime.tree.ParseTreeListener; + +/** + * This interface defines a complete listener for a parse tree produced by + * {@link DockerParser}. + */ +public interface DockerParserListener extends ParseTreeListener { + /** + * Enter a parse tree produced by {@link DockerParser#dockerfile}. + * @param ctx the parse tree + */ + void enterDockerfile(DockerParser.DockerfileContext ctx); + /** + * Exit a parse tree produced by {@link DockerParser#dockerfile}. + * @param ctx the parse tree + */ + void exitDockerfile(DockerParser.DockerfileContext ctx); + /** + * Enter a parse tree produced by {@link DockerParser#parserDirective}. + * @param ctx the parse tree + */ + void enterParserDirective(DockerParser.ParserDirectiveContext ctx); + /** + * Exit a parse tree produced by {@link DockerParser#parserDirective}. + * @param ctx the parse tree + */ + void exitParserDirective(DockerParser.ParserDirectiveContext ctx); + /** + * Enter a parse tree produced by {@link DockerParser#globalArgs}. + * @param ctx the parse tree + */ + void enterGlobalArgs(DockerParser.GlobalArgsContext ctx); + /** + * Exit a parse tree produced by {@link DockerParser#globalArgs}. + * @param ctx the parse tree + */ + void exitGlobalArgs(DockerParser.GlobalArgsContext ctx); + /** + * Enter a parse tree produced by {@link DockerParser#stage}. + * @param ctx the parse tree + */ + void enterStage(DockerParser.StageContext ctx); + /** + * Exit a parse tree produced by {@link DockerParser#stage}. + * @param ctx the parse tree + */ + void exitStage(DockerParser.StageContext ctx); + /** + * Enter a parse tree produced by {@link DockerParser#stageInstruction}. + * @param ctx the parse tree + */ + void enterStageInstruction(DockerParser.StageInstructionContext ctx); + /** + * Exit a parse tree produced by {@link DockerParser#stageInstruction}. + * @param ctx the parse tree + */ + void exitStageInstruction(DockerParser.StageInstructionContext ctx); + /** + * Enter a parse tree produced by {@link DockerParser#instruction}. + * @param ctx the parse tree + */ + void enterInstruction(DockerParser.InstructionContext ctx); + /** + * Exit a parse tree produced by {@link DockerParser#instruction}. + * @param ctx the parse tree + */ + void exitInstruction(DockerParser.InstructionContext ctx); + /** + * Enter a parse tree produced by {@link DockerParser#fromInstruction}. + * @param ctx the parse tree + */ + void enterFromInstruction(DockerParser.FromInstructionContext ctx); + /** + * Exit a parse tree produced by {@link DockerParser#fromInstruction}. + * @param ctx the parse tree + */ + void exitFromInstruction(DockerParser.FromInstructionContext ctx); + /** + * Enter a parse tree produced by {@link DockerParser#runInstruction}. + * @param ctx the parse tree + */ + void enterRunInstruction(DockerParser.RunInstructionContext ctx); + /** + * Exit a parse tree produced by {@link DockerParser#runInstruction}. + * @param ctx the parse tree + */ + void exitRunInstruction(DockerParser.RunInstructionContext ctx); + /** + * Enter a parse tree produced by {@link DockerParser#cmdInstruction}. + * @param ctx the parse tree + */ + void enterCmdInstruction(DockerParser.CmdInstructionContext ctx); + /** + * Exit a parse tree produced by {@link DockerParser#cmdInstruction}. + * @param ctx the parse tree + */ + void exitCmdInstruction(DockerParser.CmdInstructionContext ctx); + /** + * Enter a parse tree produced by {@link DockerParser#labelInstruction}. + * @param ctx the parse tree + */ + void enterLabelInstruction(DockerParser.LabelInstructionContext ctx); + /** + * Exit a parse tree produced by {@link DockerParser#labelInstruction}. + * @param ctx the parse tree + */ + void exitLabelInstruction(DockerParser.LabelInstructionContext ctx); + /** + * Enter a parse tree produced by {@link DockerParser#exposeInstruction}. + * @param ctx the parse tree + */ + void enterExposeInstruction(DockerParser.ExposeInstructionContext ctx); + /** + * Exit a parse tree produced by {@link DockerParser#exposeInstruction}. + * @param ctx the parse tree + */ + void exitExposeInstruction(DockerParser.ExposeInstructionContext ctx); + /** + * Enter a parse tree produced by {@link DockerParser#envInstruction}. + * @param ctx the parse tree + */ + void enterEnvInstruction(DockerParser.EnvInstructionContext ctx); + /** + * Exit a parse tree produced by {@link DockerParser#envInstruction}. + * @param ctx the parse tree + */ + void exitEnvInstruction(DockerParser.EnvInstructionContext ctx); + /** + * Enter a parse tree produced by {@link DockerParser#addInstruction}. + * @param ctx the parse tree + */ + void enterAddInstruction(DockerParser.AddInstructionContext ctx); + /** + * Exit a parse tree produced by {@link DockerParser#addInstruction}. + * @param ctx the parse tree + */ + void exitAddInstruction(DockerParser.AddInstructionContext ctx); + /** + * Enter a parse tree produced by {@link DockerParser#copyInstruction}. + * @param ctx the parse tree + */ + void enterCopyInstruction(DockerParser.CopyInstructionContext ctx); + /** + * Exit a parse tree produced by {@link DockerParser#copyInstruction}. + * @param ctx the parse tree + */ + void exitCopyInstruction(DockerParser.CopyInstructionContext ctx); + /** + * Enter a parse tree produced by {@link DockerParser#entrypointInstruction}. + * @param ctx the parse tree + */ + void enterEntrypointInstruction(DockerParser.EntrypointInstructionContext ctx); + /** + * Exit a parse tree produced by {@link DockerParser#entrypointInstruction}. + * @param ctx the parse tree + */ + void exitEntrypointInstruction(DockerParser.EntrypointInstructionContext ctx); + /** + * Enter a parse tree produced by {@link DockerParser#volumeInstruction}. + * @param ctx the parse tree + */ + void enterVolumeInstruction(DockerParser.VolumeInstructionContext ctx); + /** + * Exit a parse tree produced by {@link DockerParser#volumeInstruction}. + * @param ctx the parse tree + */ + void exitVolumeInstruction(DockerParser.VolumeInstructionContext ctx); + /** + * Enter a parse tree produced by {@link DockerParser#userInstruction}. + * @param ctx the parse tree + */ + void enterUserInstruction(DockerParser.UserInstructionContext ctx); + /** + * Exit a parse tree produced by {@link DockerParser#userInstruction}. + * @param ctx the parse tree + */ + void exitUserInstruction(DockerParser.UserInstructionContext ctx); + /** + * Enter a parse tree produced by {@link DockerParser#workdirInstruction}. + * @param ctx the parse tree + */ + void enterWorkdirInstruction(DockerParser.WorkdirInstructionContext ctx); + /** + * Exit a parse tree produced by {@link DockerParser#workdirInstruction}. + * @param ctx the parse tree + */ + void exitWorkdirInstruction(DockerParser.WorkdirInstructionContext ctx); + /** + * Enter a parse tree produced by {@link DockerParser#argInstruction}. + * @param ctx the parse tree + */ + void enterArgInstruction(DockerParser.ArgInstructionContext ctx); + /** + * Exit a parse tree produced by {@link DockerParser#argInstruction}. + * @param ctx the parse tree + */ + void exitArgInstruction(DockerParser.ArgInstructionContext ctx); + /** + * Enter a parse tree produced by {@link DockerParser#onbuildInstruction}. + * @param ctx the parse tree + */ + void enterOnbuildInstruction(DockerParser.OnbuildInstructionContext ctx); + /** + * Exit a parse tree produced by {@link DockerParser#onbuildInstruction}. + * @param ctx the parse tree + */ + void exitOnbuildInstruction(DockerParser.OnbuildInstructionContext ctx); + /** + * Enter a parse tree produced by {@link DockerParser#stopsignalInstruction}. + * @param ctx the parse tree + */ + void enterStopsignalInstruction(DockerParser.StopsignalInstructionContext ctx); + /** + * Exit a parse tree produced by {@link DockerParser#stopsignalInstruction}. + * @param ctx the parse tree + */ + void exitStopsignalInstruction(DockerParser.StopsignalInstructionContext ctx); + /** + * Enter a parse tree produced by {@link DockerParser#healthcheckInstruction}. + * @param ctx the parse tree + */ + void enterHealthcheckInstruction(DockerParser.HealthcheckInstructionContext ctx); + /** + * Exit a parse tree produced by {@link DockerParser#healthcheckInstruction}. + * @param ctx the parse tree + */ + void exitHealthcheckInstruction(DockerParser.HealthcheckInstructionContext ctx); + /** + * Enter a parse tree produced by {@link DockerParser#healthcheckOptions}. + * @param ctx the parse tree + */ + void enterHealthcheckOptions(DockerParser.HealthcheckOptionsContext ctx); + /** + * Exit a parse tree produced by {@link DockerParser#healthcheckOptions}. + * @param ctx the parse tree + */ + void exitHealthcheckOptions(DockerParser.HealthcheckOptionsContext ctx); + /** + * Enter a parse tree produced by {@link DockerParser#healthcheckOption}. + * @param ctx the parse tree + */ + void enterHealthcheckOption(DockerParser.HealthcheckOptionContext ctx); + /** + * Exit a parse tree produced by {@link DockerParser#healthcheckOption}. + * @param ctx the parse tree + */ + void exitHealthcheckOption(DockerParser.HealthcheckOptionContext ctx); + /** + * Enter a parse tree produced by {@link DockerParser#shellInstruction}. + * @param ctx the parse tree + */ + void enterShellInstruction(DockerParser.ShellInstructionContext ctx); + /** + * Exit a parse tree produced by {@link DockerParser#shellInstruction}. + * @param ctx the parse tree + */ + void exitShellInstruction(DockerParser.ShellInstructionContext ctx); + /** + * Enter a parse tree produced by {@link DockerParser#maintainerInstruction}. + * @param ctx the parse tree + */ + void enterMaintainerInstruction(DockerParser.MaintainerInstructionContext ctx); + /** + * Exit a parse tree produced by {@link DockerParser#maintainerInstruction}. + * @param ctx the parse tree + */ + void exitMaintainerInstruction(DockerParser.MaintainerInstructionContext ctx); + /** + * Enter a parse tree produced by {@link DockerParser#flags}. + * @param ctx the parse tree + */ + void enterFlags(DockerParser.FlagsContext ctx); + /** + * Exit a parse tree produced by {@link DockerParser#flags}. + * @param ctx the parse tree + */ + void exitFlags(DockerParser.FlagsContext ctx); + /** + * Enter a parse tree produced by {@link DockerParser#flag}. + * @param ctx the parse tree + */ + void enterFlag(DockerParser.FlagContext ctx); + /** + * Exit a parse tree produced by {@link DockerParser#flag}. + * @param ctx the parse tree + */ + void exitFlag(DockerParser.FlagContext ctx); + /** + * Enter a parse tree produced by {@link DockerParser#execForm}. + * @param ctx the parse tree + */ + void enterExecForm(DockerParser.ExecFormContext ctx); + /** + * Exit a parse tree produced by {@link DockerParser#execForm}. + * @param ctx the parse tree + */ + void exitExecForm(DockerParser.ExecFormContext ctx); + /** + * Enter a parse tree produced by {@link DockerParser#shellForm}. + * @param ctx the parse tree + */ + void enterShellForm(DockerParser.ShellFormContext ctx); + /** + * Exit a parse tree produced by {@link DockerParser#shellForm}. + * @param ctx the parse tree + */ + void exitShellForm(DockerParser.ShellFormContext ctx); + /** + * Enter a parse tree produced by {@link DockerParser#shellFormText}. + * @param ctx the parse tree + */ + void enterShellFormText(DockerParser.ShellFormTextContext ctx); + /** + * Exit a parse tree produced by {@link DockerParser#shellFormText}. + * @param ctx the parse tree + */ + void exitShellFormText(DockerParser.ShellFormTextContext ctx); + /** + * Enter a parse tree produced by {@link DockerParser#shellFormTextElement}. + * @param ctx the parse tree + */ + void enterShellFormTextElement(DockerParser.ShellFormTextElementContext ctx); + /** + * Exit a parse tree produced by {@link DockerParser#shellFormTextElement}. + * @param ctx the parse tree + */ + void exitShellFormTextElement(DockerParser.ShellFormTextElementContext ctx); + /** + * Enter a parse tree produced by {@link DockerParser#heredoc}. + * @param ctx the parse tree + */ + void enterHeredoc(DockerParser.HeredocContext ctx); + /** + * Exit a parse tree produced by {@link DockerParser#heredoc}. + * @param ctx the parse tree + */ + void exitHeredoc(DockerParser.HeredocContext ctx); + /** + * Enter a parse tree produced by {@link DockerParser#heredocPreamble}. + * @param ctx the parse tree + */ + void enterHeredocPreamble(DockerParser.HeredocPreambleContext ctx); + /** + * Exit a parse tree produced by {@link DockerParser#heredocPreamble}. + * @param ctx the parse tree + */ + void exitHeredocPreamble(DockerParser.HeredocPreambleContext ctx); + /** + * Enter a parse tree produced by {@link DockerParser#preambleElement}. + * @param ctx the parse tree + */ + void enterPreambleElement(DockerParser.PreambleElementContext ctx); + /** + * Exit a parse tree produced by {@link DockerParser#preambleElement}. + * @param ctx the parse tree + */ + void exitPreambleElement(DockerParser.PreambleElementContext ctx); + /** + * Enter a parse tree produced by {@link DockerParser#heredocBody}. + * @param ctx the parse tree + */ + void enterHeredocBody(DockerParser.HeredocBodyContext ctx); + /** + * Exit a parse tree produced by {@link DockerParser#heredocBody}. + * @param ctx the parse tree + */ + void exitHeredocBody(DockerParser.HeredocBodyContext ctx); + /** + * Enter a parse tree produced by {@link DockerParser#heredocContent}. + * @param ctx the parse tree + */ + void enterHeredocContent(DockerParser.HeredocContentContext ctx); + /** + * Exit a parse tree produced by {@link DockerParser#heredocContent}. + * @param ctx the parse tree + */ + void exitHeredocContent(DockerParser.HeredocContentContext ctx); + /** + * Enter a parse tree produced by {@link DockerParser#heredocEnd}. + * @param ctx the parse tree + */ + void enterHeredocEnd(DockerParser.HeredocEndContext ctx); + /** + * Exit a parse tree produced by {@link DockerParser#heredocEnd}. + * @param ctx the parse tree + */ + void exitHeredocEnd(DockerParser.HeredocEndContext ctx); + /** + * Enter a parse tree produced by {@link DockerParser#jsonArray}. + * @param ctx the parse tree + */ + void enterJsonArray(DockerParser.JsonArrayContext ctx); + /** + * Exit a parse tree produced by {@link DockerParser#jsonArray}. + * @param ctx the parse tree + */ + void exitJsonArray(DockerParser.JsonArrayContext ctx); + /** + * Enter a parse tree produced by {@link DockerParser#jsonArrayElements}. + * @param ctx the parse tree + */ + void enterJsonArrayElements(DockerParser.JsonArrayElementsContext ctx); + /** + * Exit a parse tree produced by {@link DockerParser#jsonArrayElements}. + * @param ctx the parse tree + */ + void exitJsonArrayElements(DockerParser.JsonArrayElementsContext ctx); + /** + * Enter a parse tree produced by {@link DockerParser#jsonString}. + * @param ctx the parse tree + */ + void enterJsonString(DockerParser.JsonStringContext ctx); + /** + * Exit a parse tree produced by {@link DockerParser#jsonString}. + * @param ctx the parse tree + */ + void exitJsonString(DockerParser.JsonStringContext ctx); + /** + * Enter a parse tree produced by {@link DockerParser#imageName}. + * @param ctx the parse tree + */ + void enterImageName(DockerParser.ImageNameContext ctx); + /** + * Exit a parse tree produced by {@link DockerParser#imageName}. + * @param ctx the parse tree + */ + void exitImageName(DockerParser.ImageNameContext ctx); + /** + * Enter a parse tree produced by {@link DockerParser#stageName}. + * @param ctx the parse tree + */ + void enterStageName(DockerParser.StageNameContext ctx); + /** + * Exit a parse tree produced by {@link DockerParser#stageName}. + * @param ctx the parse tree + */ + void exitStageName(DockerParser.StageNameContext ctx); + /** + * Enter a parse tree produced by {@link DockerParser#labelPairs}. + * @param ctx the parse tree + */ + void enterLabelPairs(DockerParser.LabelPairsContext ctx); + /** + * Exit a parse tree produced by {@link DockerParser#labelPairs}. + * @param ctx the parse tree + */ + void exitLabelPairs(DockerParser.LabelPairsContext ctx); + /** + * Enter a parse tree produced by {@link DockerParser#labelPair}. + * @param ctx the parse tree + */ + void enterLabelPair(DockerParser.LabelPairContext ctx); + /** + * Exit a parse tree produced by {@link DockerParser#labelPair}. + * @param ctx the parse tree + */ + void exitLabelPair(DockerParser.LabelPairContext ctx); + /** + * Enter a parse tree produced by {@link DockerParser#labelKey}. + * @param ctx the parse tree + */ + void enterLabelKey(DockerParser.LabelKeyContext ctx); + /** + * Exit a parse tree produced by {@link DockerParser#labelKey}. + * @param ctx the parse tree + */ + void exitLabelKey(DockerParser.LabelKeyContext ctx); + /** + * Enter a parse tree produced by {@link DockerParser#labelValue}. + * @param ctx the parse tree + */ + void enterLabelValue(DockerParser.LabelValueContext ctx); + /** + * Exit a parse tree produced by {@link DockerParser#labelValue}. + * @param ctx the parse tree + */ + void exitLabelValue(DockerParser.LabelValueContext ctx); + /** + * Enter a parse tree produced by {@link DockerParser#labelOldValue}. + * @param ctx the parse tree + */ + void enterLabelOldValue(DockerParser.LabelOldValueContext ctx); + /** + * Exit a parse tree produced by {@link DockerParser#labelOldValue}. + * @param ctx the parse tree + */ + void exitLabelOldValue(DockerParser.LabelOldValueContext ctx); + /** + * Enter a parse tree produced by {@link DockerParser#labelOldValueElement}. + * @param ctx the parse tree + */ + void enterLabelOldValueElement(DockerParser.LabelOldValueElementContext ctx); + /** + * Exit a parse tree produced by {@link DockerParser#labelOldValueElement}. + * @param ctx the parse tree + */ + void exitLabelOldValueElement(DockerParser.LabelOldValueElementContext ctx); + /** + * Enter a parse tree produced by {@link DockerParser#portList}. + * @param ctx the parse tree + */ + void enterPortList(DockerParser.PortListContext ctx); + /** + * Exit a parse tree produced by {@link DockerParser#portList}. + * @param ctx the parse tree + */ + void exitPortList(DockerParser.PortListContext ctx); + /** + * Enter a parse tree produced by {@link DockerParser#port}. + * @param ctx the parse tree + */ + void enterPort(DockerParser.PortContext ctx); + /** + * Exit a parse tree produced by {@link DockerParser#port}. + * @param ctx the parse tree + */ + void exitPort(DockerParser.PortContext ctx); + /** + * Enter a parse tree produced by {@link DockerParser#envPairs}. + * @param ctx the parse tree + */ + void enterEnvPairs(DockerParser.EnvPairsContext ctx); + /** + * Exit a parse tree produced by {@link DockerParser#envPairs}. + * @param ctx the parse tree + */ + void exitEnvPairs(DockerParser.EnvPairsContext ctx); + /** + * Enter a parse tree produced by {@link DockerParser#envPair}. + * @param ctx the parse tree + */ + void enterEnvPair(DockerParser.EnvPairContext ctx); + /** + * Exit a parse tree produced by {@link DockerParser#envPair}. + * @param ctx the parse tree + */ + void exitEnvPair(DockerParser.EnvPairContext ctx); + /** + * Enter a parse tree produced by {@link DockerParser#envKey}. + * @param ctx the parse tree + */ + void enterEnvKey(DockerParser.EnvKeyContext ctx); + /** + * Exit a parse tree produced by {@link DockerParser#envKey}. + * @param ctx the parse tree + */ + void exitEnvKey(DockerParser.EnvKeyContext ctx); + /** + * Enter a parse tree produced by {@link DockerParser#envValueEquals}. + * @param ctx the parse tree + */ + void enterEnvValueEquals(DockerParser.EnvValueEqualsContext ctx); + /** + * Exit a parse tree produced by {@link DockerParser#envValueEquals}. + * @param ctx the parse tree + */ + void exitEnvValueEquals(DockerParser.EnvValueEqualsContext ctx); + /** + * Enter a parse tree produced by {@link DockerParser#envValueSpace}. + * @param ctx the parse tree + */ + void enterEnvValueSpace(DockerParser.EnvValueSpaceContext ctx); + /** + * Exit a parse tree produced by {@link DockerParser#envValueSpace}. + * @param ctx the parse tree + */ + void exitEnvValueSpace(DockerParser.EnvValueSpaceContext ctx); + /** + * Enter a parse tree produced by {@link DockerParser#envTextEquals}. + * @param ctx the parse tree + */ + void enterEnvTextEquals(DockerParser.EnvTextEqualsContext ctx); + /** + * Exit a parse tree produced by {@link DockerParser#envTextEquals}. + * @param ctx the parse tree + */ + void exitEnvTextEquals(DockerParser.EnvTextEqualsContext ctx); + /** + * Enter a parse tree produced by {@link DockerParser#envTextElementEquals}. + * @param ctx the parse tree + */ + void enterEnvTextElementEquals(DockerParser.EnvTextElementEqualsContext ctx); + /** + * Exit a parse tree produced by {@link DockerParser#envTextElementEquals}. + * @param ctx the parse tree + */ + void exitEnvTextElementEquals(DockerParser.EnvTextElementEqualsContext ctx); + /** + * Enter a parse tree produced by {@link DockerParser#sourceList}. + * @param ctx the parse tree + */ + void enterSourceList(DockerParser.SourceListContext ctx); + /** + * Exit a parse tree produced by {@link DockerParser#sourceList}. + * @param ctx the parse tree + */ + void exitSourceList(DockerParser.SourceListContext ctx); + /** + * Enter a parse tree produced by {@link DockerParser#sourcePath}. + * @param ctx the parse tree + */ + void enterSourcePath(DockerParser.SourcePathContext ctx); + /** + * Exit a parse tree produced by {@link DockerParser#sourcePath}. + * @param ctx the parse tree + */ + void exitSourcePath(DockerParser.SourcePathContext ctx); + /** + * Enter a parse tree produced by {@link DockerParser#destination}. + * @param ctx the parse tree + */ + void enterDestination(DockerParser.DestinationContext ctx); + /** + * Exit a parse tree produced by {@link DockerParser#destination}. + * @param ctx the parse tree + */ + void exitDestination(DockerParser.DestinationContext ctx); + /** + * Enter a parse tree produced by {@link DockerParser#destinationPath}. + * @param ctx the parse tree + */ + void enterDestinationPath(DockerParser.DestinationPathContext ctx); + /** + * Exit a parse tree produced by {@link DockerParser#destinationPath}. + * @param ctx the parse tree + */ + void exitDestinationPath(DockerParser.DestinationPathContext ctx); + /** + * Enter a parse tree produced by {@link DockerParser#path}. + * @param ctx the parse tree + */ + void enterPath(DockerParser.PathContext ctx); + /** + * Exit a parse tree produced by {@link DockerParser#path}. + * @param ctx the parse tree + */ + void exitPath(DockerParser.PathContext ctx); + /** + * Enter a parse tree produced by {@link DockerParser#pathList}. + * @param ctx the parse tree + */ + void enterPathList(DockerParser.PathListContext ctx); + /** + * Exit a parse tree produced by {@link DockerParser#pathList}. + * @param ctx the parse tree + */ + void exitPathList(DockerParser.PathListContext ctx); + /** + * Enter a parse tree produced by {@link DockerParser#volumePath}. + * @param ctx the parse tree + */ + void enterVolumePath(DockerParser.VolumePathContext ctx); + /** + * Exit a parse tree produced by {@link DockerParser#volumePath}. + * @param ctx the parse tree + */ + void exitVolumePath(DockerParser.VolumePathContext ctx); + /** + * Enter a parse tree produced by {@link DockerParser#userSpec}. + * @param ctx the parse tree + */ + void enterUserSpec(DockerParser.UserSpecContext ctx); + /** + * Exit a parse tree produced by {@link DockerParser#userSpec}. + * @param ctx the parse tree + */ + void exitUserSpec(DockerParser.UserSpecContext ctx); + /** + * Enter a parse tree produced by {@link DockerParser#argName}. + * @param ctx the parse tree + */ + void enterArgName(DockerParser.ArgNameContext ctx); + /** + * Exit a parse tree produced by {@link DockerParser#argName}. + * @param ctx the parse tree + */ + void exitArgName(DockerParser.ArgNameContext ctx); + /** + * Enter a parse tree produced by {@link DockerParser#argValue}. + * @param ctx the parse tree + */ + void enterArgValue(DockerParser.ArgValueContext ctx); + /** + * Exit a parse tree produced by {@link DockerParser#argValue}. + * @param ctx the parse tree + */ + void exitArgValue(DockerParser.ArgValueContext ctx); + /** + * Enter a parse tree produced by {@link DockerParser#signal}. + * @param ctx the parse tree + */ + void enterSignal(DockerParser.SignalContext ctx); + /** + * Exit a parse tree produced by {@link DockerParser#signal}. + * @param ctx the parse tree + */ + void exitSignal(DockerParser.SignalContext ctx); + /** + * Enter a parse tree produced by {@link DockerParser#text}. + * @param ctx the parse tree + */ + void enterText(DockerParser.TextContext ctx); + /** + * Exit a parse tree produced by {@link DockerParser#text}. + * @param ctx the parse tree + */ + void exitText(DockerParser.TextContext ctx); + /** + * Enter a parse tree produced by {@link DockerParser#textElement}. + * @param ctx the parse tree + */ + void enterTextElement(DockerParser.TextElementContext ctx); + /** + * Exit a parse tree produced by {@link DockerParser#textElement}. + * @param ctx the parse tree + */ + void exitTextElement(DockerParser.TextElementContext ctx); +} \ No newline at end of file diff --git a/rewrite-docker/src/main/java/org/openrewrite/docker/internal/grammar/DockerParserVisitor.java b/rewrite-docker/src/main/java/org/openrewrite/docker/internal/grammar/DockerParserVisitor.java new file mode 100644 index 0000000000..9cfe840a0a --- /dev/null +++ b/rewrite-docker/src/main/java/org/openrewrite/docker/internal/grammar/DockerParserVisitor.java @@ -0,0 +1,454 @@ +/* + * Copyright 2026 the original author or authors. + *

+ * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * https://www.apache.org/licenses/LICENSE-2.0 + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +// Generated from /home/tim/Documents/workspace/openrewrite/rewrite/rewrite-docker/src/main/antlr/DockerParser.g4 by ANTLR 4.13.2 +package org.openrewrite.docker.internal.grammar; +import org.antlr.v4.runtime.tree.ParseTreeVisitor; + +/** + * This interface defines a complete generic visitor for a parse tree produced + * by {@link DockerParser}. + * + * @param The return type of the visit operation. Use {@link Void} for + * operations with no return type. + */ +public interface DockerParserVisitor extends ParseTreeVisitor { + /** + * Visit a parse tree produced by {@link DockerParser#dockerfile}. + * @param ctx the parse tree + * @return the visitor result + */ + T visitDockerfile(DockerParser.DockerfileContext ctx); + /** + * Visit a parse tree produced by {@link DockerParser#parserDirective}. + * @param ctx the parse tree + * @return the visitor result + */ + T visitParserDirective(DockerParser.ParserDirectiveContext ctx); + /** + * Visit a parse tree produced by {@link DockerParser#globalArgs}. + * @param ctx the parse tree + * @return the visitor result + */ + T visitGlobalArgs(DockerParser.GlobalArgsContext ctx); + /** + * Visit a parse tree produced by {@link DockerParser#stage}. + * @param ctx the parse tree + * @return the visitor result + */ + T visitStage(DockerParser.StageContext ctx); + /** + * Visit a parse tree produced by {@link DockerParser#stageInstruction}. + * @param ctx the parse tree + * @return the visitor result + */ + T visitStageInstruction(DockerParser.StageInstructionContext ctx); + /** + * Visit a parse tree produced by {@link DockerParser#instruction}. + * @param ctx the parse tree + * @return the visitor result + */ + T visitInstruction(DockerParser.InstructionContext ctx); + /** + * Visit a parse tree produced by {@link DockerParser#fromInstruction}. + * @param ctx the parse tree + * @return the visitor result + */ + T visitFromInstruction(DockerParser.FromInstructionContext ctx); + /** + * Visit a parse tree produced by {@link DockerParser#runInstruction}. + * @param ctx the parse tree + * @return the visitor result + */ + T visitRunInstruction(DockerParser.RunInstructionContext ctx); + /** + * Visit a parse tree produced by {@link DockerParser#cmdInstruction}. + * @param ctx the parse tree + * @return the visitor result + */ + T visitCmdInstruction(DockerParser.CmdInstructionContext ctx); + /** + * Visit a parse tree produced by {@link DockerParser#labelInstruction}. + * @param ctx the parse tree + * @return the visitor result + */ + T visitLabelInstruction(DockerParser.LabelInstructionContext ctx); + /** + * Visit a parse tree produced by {@link DockerParser#exposeInstruction}. + * @param ctx the parse tree + * @return the visitor result + */ + T visitExposeInstruction(DockerParser.ExposeInstructionContext ctx); + /** + * Visit a parse tree produced by {@link DockerParser#envInstruction}. + * @param ctx the parse tree + * @return the visitor result + */ + T visitEnvInstruction(DockerParser.EnvInstructionContext ctx); + /** + * Visit a parse tree produced by {@link DockerParser#addInstruction}. + * @param ctx the parse tree + * @return the visitor result + */ + T visitAddInstruction(DockerParser.AddInstructionContext ctx); + /** + * Visit a parse tree produced by {@link DockerParser#copyInstruction}. + * @param ctx the parse tree + * @return the visitor result + */ + T visitCopyInstruction(DockerParser.CopyInstructionContext ctx); + /** + * Visit a parse tree produced by {@link DockerParser#entrypointInstruction}. + * @param ctx the parse tree + * @return the visitor result + */ + T visitEntrypointInstruction(DockerParser.EntrypointInstructionContext ctx); + /** + * Visit a parse tree produced by {@link DockerParser#volumeInstruction}. + * @param ctx the parse tree + * @return the visitor result + */ + T visitVolumeInstruction(DockerParser.VolumeInstructionContext ctx); + /** + * Visit a parse tree produced by {@link DockerParser#userInstruction}. + * @param ctx the parse tree + * @return the visitor result + */ + T visitUserInstruction(DockerParser.UserInstructionContext ctx); + /** + * Visit a parse tree produced by {@link DockerParser#workdirInstruction}. + * @param ctx the parse tree + * @return the visitor result + */ + T visitWorkdirInstruction(DockerParser.WorkdirInstructionContext ctx); + /** + * Visit a parse tree produced by {@link DockerParser#argInstruction}. + * @param ctx the parse tree + * @return the visitor result + */ + T visitArgInstruction(DockerParser.ArgInstructionContext ctx); + /** + * Visit a parse tree produced by {@link DockerParser#onbuildInstruction}. + * @param ctx the parse tree + * @return the visitor result + */ + T visitOnbuildInstruction(DockerParser.OnbuildInstructionContext ctx); + /** + * Visit a parse tree produced by {@link DockerParser#stopsignalInstruction}. + * @param ctx the parse tree + * @return the visitor result + */ + T visitStopsignalInstruction(DockerParser.StopsignalInstructionContext ctx); + /** + * Visit a parse tree produced by {@link DockerParser#healthcheckInstruction}. + * @param ctx the parse tree + * @return the visitor result + */ + T visitHealthcheckInstruction(DockerParser.HealthcheckInstructionContext ctx); + /** + * Visit a parse tree produced by {@link DockerParser#healthcheckOptions}. + * @param ctx the parse tree + * @return the visitor result + */ + T visitHealthcheckOptions(DockerParser.HealthcheckOptionsContext ctx); + /** + * Visit a parse tree produced by {@link DockerParser#healthcheckOption}. + * @param ctx the parse tree + * @return the visitor result + */ + T visitHealthcheckOption(DockerParser.HealthcheckOptionContext ctx); + /** + * Visit a parse tree produced by {@link DockerParser#shellInstruction}. + * @param ctx the parse tree + * @return the visitor result + */ + T visitShellInstruction(DockerParser.ShellInstructionContext ctx); + /** + * Visit a parse tree produced by {@link DockerParser#maintainerInstruction}. + * @param ctx the parse tree + * @return the visitor result + */ + T visitMaintainerInstruction(DockerParser.MaintainerInstructionContext ctx); + /** + * Visit a parse tree produced by {@link DockerParser#flags}. + * @param ctx the parse tree + * @return the visitor result + */ + T visitFlags(DockerParser.FlagsContext ctx); + /** + * Visit a parse tree produced by {@link DockerParser#flag}. + * @param ctx the parse tree + * @return the visitor result + */ + T visitFlag(DockerParser.FlagContext ctx); + /** + * Visit a parse tree produced by {@link DockerParser#execForm}. + * @param ctx the parse tree + * @return the visitor result + */ + T visitExecForm(DockerParser.ExecFormContext ctx); + /** + * Visit a parse tree produced by {@link DockerParser#shellForm}. + * @param ctx the parse tree + * @return the visitor result + */ + T visitShellForm(DockerParser.ShellFormContext ctx); + /** + * Visit a parse tree produced by {@link DockerParser#shellFormText}. + * @param ctx the parse tree + * @return the visitor result + */ + T visitShellFormText(DockerParser.ShellFormTextContext ctx); + /** + * Visit a parse tree produced by {@link DockerParser#shellFormTextElement}. + * @param ctx the parse tree + * @return the visitor result + */ + T visitShellFormTextElement(DockerParser.ShellFormTextElementContext ctx); + /** + * Visit a parse tree produced by {@link DockerParser#heredoc}. + * @param ctx the parse tree + * @return the visitor result + */ + T visitHeredoc(DockerParser.HeredocContext ctx); + /** + * Visit a parse tree produced by {@link DockerParser#heredocPreamble}. + * @param ctx the parse tree + * @return the visitor result + */ + T visitHeredocPreamble(DockerParser.HeredocPreambleContext ctx); + /** + * Visit a parse tree produced by {@link DockerParser#preambleElement}. + * @param ctx the parse tree + * @return the visitor result + */ + T visitPreambleElement(DockerParser.PreambleElementContext ctx); + /** + * Visit a parse tree produced by {@link DockerParser#heredocBody}. + * @param ctx the parse tree + * @return the visitor result + */ + T visitHeredocBody(DockerParser.HeredocBodyContext ctx); + /** + * Visit a parse tree produced by {@link DockerParser#heredocContent}. + * @param ctx the parse tree + * @return the visitor result + */ + T visitHeredocContent(DockerParser.HeredocContentContext ctx); + /** + * Visit a parse tree produced by {@link DockerParser#heredocEnd}. + * @param ctx the parse tree + * @return the visitor result + */ + T visitHeredocEnd(DockerParser.HeredocEndContext ctx); + /** + * Visit a parse tree produced by {@link DockerParser#jsonArray}. + * @param ctx the parse tree + * @return the visitor result + */ + T visitJsonArray(DockerParser.JsonArrayContext ctx); + /** + * Visit a parse tree produced by {@link DockerParser#jsonArrayElements}. + * @param ctx the parse tree + * @return the visitor result + */ + T visitJsonArrayElements(DockerParser.JsonArrayElementsContext ctx); + /** + * Visit a parse tree produced by {@link DockerParser#jsonString}. + * @param ctx the parse tree + * @return the visitor result + */ + T visitJsonString(DockerParser.JsonStringContext ctx); + /** + * Visit a parse tree produced by {@link DockerParser#imageName}. + * @param ctx the parse tree + * @return the visitor result + */ + T visitImageName(DockerParser.ImageNameContext ctx); + /** + * Visit a parse tree produced by {@link DockerParser#stageName}. + * @param ctx the parse tree + * @return the visitor result + */ + T visitStageName(DockerParser.StageNameContext ctx); + /** + * Visit a parse tree produced by {@link DockerParser#labelPairs}. + * @param ctx the parse tree + * @return the visitor result + */ + T visitLabelPairs(DockerParser.LabelPairsContext ctx); + /** + * Visit a parse tree produced by {@link DockerParser#labelPair}. + * @param ctx the parse tree + * @return the visitor result + */ + T visitLabelPair(DockerParser.LabelPairContext ctx); + /** + * Visit a parse tree produced by {@link DockerParser#labelKey}. + * @param ctx the parse tree + * @return the visitor result + */ + T visitLabelKey(DockerParser.LabelKeyContext ctx); + /** + * Visit a parse tree produced by {@link DockerParser#labelValue}. + * @param ctx the parse tree + * @return the visitor result + */ + T visitLabelValue(DockerParser.LabelValueContext ctx); + /** + * Visit a parse tree produced by {@link DockerParser#labelOldValue}. + * @param ctx the parse tree + * @return the visitor result + */ + T visitLabelOldValue(DockerParser.LabelOldValueContext ctx); + /** + * Visit a parse tree produced by {@link DockerParser#labelOldValueElement}. + * @param ctx the parse tree + * @return the visitor result + */ + T visitLabelOldValueElement(DockerParser.LabelOldValueElementContext ctx); + /** + * Visit a parse tree produced by {@link DockerParser#portList}. + * @param ctx the parse tree + * @return the visitor result + */ + T visitPortList(DockerParser.PortListContext ctx); + /** + * Visit a parse tree produced by {@link DockerParser#port}. + * @param ctx the parse tree + * @return the visitor result + */ + T visitPort(DockerParser.PortContext ctx); + /** + * Visit a parse tree produced by {@link DockerParser#envPairs}. + * @param ctx the parse tree + * @return the visitor result + */ + T visitEnvPairs(DockerParser.EnvPairsContext ctx); + /** + * Visit a parse tree produced by {@link DockerParser#envPair}. + * @param ctx the parse tree + * @return the visitor result + */ + T visitEnvPair(DockerParser.EnvPairContext ctx); + /** + * Visit a parse tree produced by {@link DockerParser#envKey}. + * @param ctx the parse tree + * @return the visitor result + */ + T visitEnvKey(DockerParser.EnvKeyContext ctx); + /** + * Visit a parse tree produced by {@link DockerParser#envValueEquals}. + * @param ctx the parse tree + * @return the visitor result + */ + T visitEnvValueEquals(DockerParser.EnvValueEqualsContext ctx); + /** + * Visit a parse tree produced by {@link DockerParser#envValueSpace}. + * @param ctx the parse tree + * @return the visitor result + */ + T visitEnvValueSpace(DockerParser.EnvValueSpaceContext ctx); + /** + * Visit a parse tree produced by {@link DockerParser#envTextEquals}. + * @param ctx the parse tree + * @return the visitor result + */ + T visitEnvTextEquals(DockerParser.EnvTextEqualsContext ctx); + /** + * Visit a parse tree produced by {@link DockerParser#envTextElementEquals}. + * @param ctx the parse tree + * @return the visitor result + */ + T visitEnvTextElementEquals(DockerParser.EnvTextElementEqualsContext ctx); + /** + * Visit a parse tree produced by {@link DockerParser#sourceList}. + * @param ctx the parse tree + * @return the visitor result + */ + T visitSourceList(DockerParser.SourceListContext ctx); + /** + * Visit a parse tree produced by {@link DockerParser#sourcePath}. + * @param ctx the parse tree + * @return the visitor result + */ + T visitSourcePath(DockerParser.SourcePathContext ctx); + /** + * Visit a parse tree produced by {@link DockerParser#destination}. + * @param ctx the parse tree + * @return the visitor result + */ + T visitDestination(DockerParser.DestinationContext ctx); + /** + * Visit a parse tree produced by {@link DockerParser#destinationPath}. + * @param ctx the parse tree + * @return the visitor result + */ + T visitDestinationPath(DockerParser.DestinationPathContext ctx); + /** + * Visit a parse tree produced by {@link DockerParser#path}. + * @param ctx the parse tree + * @return the visitor result + */ + T visitPath(DockerParser.PathContext ctx); + /** + * Visit a parse tree produced by {@link DockerParser#pathList}. + * @param ctx the parse tree + * @return the visitor result + */ + T visitPathList(DockerParser.PathListContext ctx); + /** + * Visit a parse tree produced by {@link DockerParser#volumePath}. + * @param ctx the parse tree + * @return the visitor result + */ + T visitVolumePath(DockerParser.VolumePathContext ctx); + /** + * Visit a parse tree produced by {@link DockerParser#userSpec}. + * @param ctx the parse tree + * @return the visitor result + */ + T visitUserSpec(DockerParser.UserSpecContext ctx); + /** + * Visit a parse tree produced by {@link DockerParser#argName}. + * @param ctx the parse tree + * @return the visitor result + */ + T visitArgName(DockerParser.ArgNameContext ctx); + /** + * Visit a parse tree produced by {@link DockerParser#argValue}. + * @param ctx the parse tree + * @return the visitor result + */ + T visitArgValue(DockerParser.ArgValueContext ctx); + /** + * Visit a parse tree produced by {@link DockerParser#signal}. + * @param ctx the parse tree + * @return the visitor result + */ + T visitSignal(DockerParser.SignalContext ctx); + /** + * Visit a parse tree produced by {@link DockerParser#text}. + * @param ctx the parse tree + * @return the visitor result + */ + T visitText(DockerParser.TextContext ctx); + /** + * Visit a parse tree produced by {@link DockerParser#textElement}. + * @param ctx the parse tree + * @return the visitor result + */ + T visitTextElement(DockerParser.TextElementContext ctx); +} \ No newline at end of file diff --git a/rewrite-docker/src/main/java/org/openrewrite/docker/internal/package-info.java b/rewrite-docker/src/main/java/org/openrewrite/docker/internal/package-info.java new file mode 100644 index 0000000000..e429bf8d67 --- /dev/null +++ b/rewrite-docker/src/main/java/org/openrewrite/docker/internal/package-info.java @@ -0,0 +1,19 @@ +/* + * Copyright 2026 the original author or authors. + *

+ * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * https://www.apache.org/licenses/LICENSE-2.0 + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +@NullMarked +package org.openrewrite.docker.internal; + +import org.jspecify.annotations.NullMarked; diff --git a/rewrite-docker/src/main/java/org/openrewrite/docker/package-info.java b/rewrite-docker/src/main/java/org/openrewrite/docker/package-info.java new file mode 100644 index 0000000000..e427ea2d4a --- /dev/null +++ b/rewrite-docker/src/main/java/org/openrewrite/docker/package-info.java @@ -0,0 +1,20 @@ +/* + * Copyright 2026 the original author or authors. + *

+ * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * https://www.apache.org/licenses/LICENSE-2.0 + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +@NullMarked +@NonNullFields +package org.openrewrite.docker; +import org.jspecify.annotations.NullMarked; +import org.openrewrite.internal.lang.NonNullFields; diff --git a/rewrite-docker/src/main/java/org/openrewrite/docker/search/FindDockerBaseImages.java b/rewrite-docker/src/main/java/org/openrewrite/docker/search/FindDockerBaseImages.java new file mode 100644 index 0000000000..052b073b77 --- /dev/null +++ b/rewrite-docker/src/main/java/org/openrewrite/docker/search/FindDockerBaseImages.java @@ -0,0 +1,140 @@ +/* + * Copyright 2026 the original author or authors. + *

+ * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * https://www.apache.org/licenses/LICENSE-2.0 + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.openrewrite.docker.search; + +import lombok.EqualsAndHashCode; +import lombok.Value; +import org.jspecify.annotations.Nullable; +import org.openrewrite.ExecutionContext; +import org.openrewrite.Option; +import org.openrewrite.Recipe; +import org.openrewrite.TreeVisitor; +import org.openrewrite.docker.DockerIsoVisitor; +import org.openrewrite.docker.table.DockerBaseImages; +import org.openrewrite.docker.tree.Docker; +import org.openrewrite.internal.StringUtils; +import org.openrewrite.marker.SearchResult; + +@Value +@EqualsAndHashCode(callSuper = false) +public class FindDockerBaseImages extends Recipe { + + transient DockerBaseImages dockerBaseImages = new DockerBaseImages(this); + + @Option(displayName = "Image name pattern", + description = "A glob pattern to match against base image names. If not specified, all base images are matched.", + example = "ubuntu*", + required = false) + @Nullable + String imageNamePattern; + + @Override + public String getDisplayName() { + return "Find Docker base images"; + } + + @Override + public String getDescription() { + return "Find all base images (FROM instructions) in Dockerfiles."; + } + + @Override + public TreeVisitor getVisitor() { + return new DockerIsoVisitor() { + @Override + public Docker.From visitFrom(Docker.From from, ExecutionContext ctx) { + Docker.From f = super.visitFrom(from, ctx); + + String imageName = extractText(f.getImageName()); + if (imageName == null) { + // Contains unresolved environment variables + return f; + } + + // Check if the image name matches the pattern (if specified) + if (imageNamePattern != null && !StringUtils.matchesGlob(imageName, imageNamePattern)) { + return f; + } + + String tag = extractText(f.getTag()); + String digest = extractText(f.getDigest()); + String platform = getPlatformFlag(f); + String stageName = getAsName(f); + + // Insert row into data table + dockerBaseImages.insertRow(ctx, new DockerBaseImages.Row( + getCursor().firstEnclosingOrThrow(Docker.File.class).getSourcePath().toString(), + stageName, + imageName, + tag, + digest, + platform + )); + + // Build message with image reference + String message = imageName; + if (digest != null) { + message += "@" + digest; + } else if (tag != null) { + message += ":" + tag; + } + + // Mark the FROM instruction as a search result + return SearchResult.found(f, message); + } + + private @Nullable String extractText(Docker.@Nullable Argument arg) { + if (arg == null) { + return null; + } + StringBuilder builder = new StringBuilder(); + for (Docker.ArgumentContent content : arg.getContents()) { + if (content instanceof Docker.Literal) { + builder.append(((Docker.Literal) content).getText()); + } else if (content instanceof Docker.EnvironmentVariable) { + Docker.EnvironmentVariable env = (Docker.EnvironmentVariable) content; + // Include the variable reference as-is (e.g., ${VAR} or $VAR) + if (env.isBraced()) { + builder.append("${").append(env.getName()).append("}"); + } else { + builder.append("$").append(env.getName()); + } + } + } + return builder.toString(); + } + + private @Nullable String getPlatformFlag(Docker.From from) { + if (from.getFlags() == null) { + return null; + } + for (Docker.Flag flag : from.getFlags()) { + if ("platform".equals(flag.getName()) && flag.getValue() != null) { + return extractText(flag.getValue()); + } + } + return null; + } + + private @Nullable String getAsName(Docker.From from) { + if (from.getAs() == null) { + return null; + } + return from.getAs().getName().getText(); + } + }; + } +} diff --git a/rewrite-docker/src/main/java/org/openrewrite/docker/search/FindExposedPorts.java b/rewrite-docker/src/main/java/org/openrewrite/docker/search/FindExposedPorts.java new file mode 100644 index 0000000000..9fa2169d56 --- /dev/null +++ b/rewrite-docker/src/main/java/org/openrewrite/docker/search/FindExposedPorts.java @@ -0,0 +1,135 @@ +/* + * Copyright 2026 the original author or authors. + *

+ * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * https://www.apache.org/licenses/LICENSE-2.0 + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.openrewrite.docker.search; + +import lombok.EqualsAndHashCode; +import lombok.Value; +import org.jspecify.annotations.Nullable; +import org.openrewrite.ExecutionContext; +import org.openrewrite.Option; +import org.openrewrite.Recipe; +import org.openrewrite.TreeVisitor; +import org.openrewrite.docker.DockerIsoVisitor; +import org.openrewrite.docker.table.DockerExposedPorts; +import org.openrewrite.docker.tree.Docker; +import org.openrewrite.internal.StringUtils; +import org.openrewrite.marker.SearchResult; + +import java.util.ArrayList; +import java.util.List; + +@Value +@EqualsAndHashCode(callSuper = false) +public class FindExposedPorts extends Recipe { + + transient DockerExposedPorts exposedPorts = new DockerExposedPorts(this); + + @Option(displayName = "Port pattern", + description = "A glob pattern to filter ports. For example, '80*' to find ports starting with 80. " + + "If not specified, all EXPOSE instructions are matched.", + example = "80*", + required = false) + @Nullable + String portPattern; + + @Override + public String getDisplayName() { + return "Find exposed ports"; + } + + @Override + public String getDescription() { + return "Find all EXPOSE instructions in Dockerfiles and report the exposed ports."; + } + + @Override + public TreeVisitor getVisitor() { + return new DockerIsoVisitor() { + + @Override + public Docker.Expose visitExpose(Docker.Expose expose, ExecutionContext ctx) { + Docker.Expose e = super.visitExpose(expose, ctx); + + String sourceFile = getCursor().firstEnclosingOrThrow(Docker.File.class) + .getSourcePath().toString(); + + // Get the stage name if available + String stageName = null; + Docker.Stage stage = getCursor().firstEnclosing(Docker.Stage.class); + if (stage != null && stage.getFrom().getAs() != null) { + stageName = stage.getFrom().getAs().getName().getText(); + } + + List matchedPorts = new ArrayList<>(); + + for (Docker.Port port : e.getPorts()) { + String portSpec = port.getText(); + + // Check if port matches pattern + if (portPattern != null && !StringUtils.matchesGlob(portSpec, portPattern)) { + continue; + } + + matchedPorts.add(portSpec); + + // Get port number(s) as string for the data table + String portNumber; + if (port.isVariable()) { + portNumber = portSpec; // Contains variable, use raw text + } else if (port.isRange()) { + portNumber = port.getStart() + "-" + port.getEnd(); + } else { + portNumber = String.valueOf(port.getStart()); + } + + // Only report protocol if explicitly specified in the port text + String protocol = null; + if (portSpec.contains("/")) { + protocol = port.getProtocol() == Docker.Port.Protocol.UDP ? "udp" : "tcp"; + } + + exposedPorts.insertRow(ctx, new DockerExposedPorts.Row( + sourceFile, + stageName, + portNumber, + protocol + )); + } + + if (!matchedPorts.isEmpty()) { + return SearchResult.found(e, String.join(", ", matchedPorts)); + } + + return e; + } + + private @Nullable String extractText(Docker.@Nullable Argument arg) { + if (arg == null) { + return null; + } + StringBuilder builder = new StringBuilder(); + for (Docker.ArgumentContent content : arg.getContents()) { + if (content instanceof Docker.Literal) { + builder.append(((Docker.Literal) content).getText()); + } else if (content instanceof Docker.EnvironmentVariable) { + return null; + } + } + return builder.toString(); + } + }; + } +} diff --git a/rewrite-docker/src/main/java/org/openrewrite/docker/search/FindMissingHealthcheck.java b/rewrite-docker/src/main/java/org/openrewrite/docker/search/FindMissingHealthcheck.java new file mode 100644 index 0000000000..3105484a95 --- /dev/null +++ b/rewrite-docker/src/main/java/org/openrewrite/docker/search/FindMissingHealthcheck.java @@ -0,0 +1,74 @@ +/* + * Copyright 2026 the original author or authors. + *

+ * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * https://www.apache.org/licenses/LICENSE-2.0 + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.openrewrite.docker.search; + +import lombok.EqualsAndHashCode; +import lombok.Value; +import org.openrewrite.ExecutionContext; +import org.openrewrite.Recipe; +import org.openrewrite.TreeVisitor; +import org.openrewrite.docker.DockerIsoVisitor; +import org.openrewrite.docker.tree.Docker; +import org.openrewrite.internal.ListUtils; +import org.openrewrite.marker.SearchResult; + +/** + * Finds Dockerfiles where the final stage is missing a HEALTHCHECK instruction. + */ +@Value +@EqualsAndHashCode(callSuper = false) +public class FindMissingHealthcheck extends Recipe { + + @Override + public String getDisplayName() { + return "Find missing HEALTHCHECK"; + } + + @Override + public String getDescription() { + return "Finds Dockerfiles where the final stage is missing a HEALTHCHECK instruction. " + + "Health checks help container orchestrators determine if a container is healthy and ready to receive traffic."; + } + + @Override + public TreeVisitor getVisitor() { + return new DockerIsoVisitor() { + + @Override + public Docker.File visitFile(Docker.File file, ExecutionContext ctx) { + Docker.File f = super.visitFile(file, ctx); + + if (f.getStages().isEmpty()) { + return f; + } + + // Check final stage for missing HEALTHCHECK + Docker.Stage finalStage = f.getStages().get(f.getStages().size() - 1); + + boolean hasHealthcheck = finalStage.getInstructions().stream() + .anyMatch(inst -> inst instanceof Docker.Healthcheck); + + if (!hasHealthcheck) { + // Mark the FROM instruction of the final stage + f = f.withStages(ListUtils.mapLast(f.getStages(), stage -> + stage.withFrom(SearchResult.found(stage.getFrom(), "Missing HEALTHCHECK instruction")))); + } + + return f; + } + }; + } +} diff --git a/rewrite-docker/src/main/java/org/openrewrite/docker/search/FindRootUser.java b/rewrite-docker/src/main/java/org/openrewrite/docker/search/FindRootUser.java new file mode 100644 index 0000000000..c0c9e89f51 --- /dev/null +++ b/rewrite-docker/src/main/java/org/openrewrite/docker/search/FindRootUser.java @@ -0,0 +1,113 @@ +/* + * Copyright 2026 the original author or authors. + *

+ * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * https://www.apache.org/licenses/LICENSE-2.0 + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.openrewrite.docker.search; + +import lombok.EqualsAndHashCode; +import lombok.Value; +import org.jspecify.annotations.Nullable; +import org.openrewrite.ExecutionContext; +import org.openrewrite.Option; +import org.openrewrite.Recipe; +import org.openrewrite.TreeVisitor; +import org.openrewrite.docker.DockerIsoVisitor; +import org.openrewrite.docker.tree.Docker; +import org.openrewrite.internal.ListUtils; +import org.openrewrite.marker.SearchResult; + +/** + * Finds containers that run as root, either explicitly via USER root/0 + * or implicitly by not having a USER instruction in the final stage. + */ +@Value +@EqualsAndHashCode(callSuper = false) +public class FindRootUser extends Recipe { + + @Option(displayName = "Include missing USER", + description = "When true, also marks the final stage if no USER instruction is present (defaults to root). " + + "When false, only marks explicit USER root/0 instructions.", + required = false) + @Nullable + Boolean includeMissingUser; + + @Override + public String getDisplayName() { + return "Find containers running as root"; + } + + @Override + public String getDescription() { + return "Finds containers that run as root user. This includes explicit `USER root` or `USER 0` instructions, " + + "and optionally containers with no USER instruction in the final stage (which default to root)."; + } + + @Override + public TreeVisitor getVisitor() { + boolean checkMissing = includeMissingUser == null || includeMissingUser; + + return new DockerIsoVisitor() { + + @Override + public Docker.File visitFile(Docker.File file, ExecutionContext ctx) { + Docker.File f = super.visitFile(file, ctx); + + if (!checkMissing || f.getStages().isEmpty()) { + return f; + } + + // Check final stage for missing USER instruction + Docker.Stage finalStage = f.getStages().get(f.getStages().size() - 1); + + boolean hasUserInstruction = finalStage.getInstructions().stream() + .anyMatch(inst -> inst instanceof Docker.User); + + if (!hasUserInstruction) { + // Mark the FROM instruction of the final stage + f = f.withStages(ListUtils.mapLast(f.getStages(), stage -> + stage.withFrom(SearchResult.found(stage.getFrom(), "No USER instruction, runs as root")))); + } + + return f; + } + + @Override + public Docker.User visitUser(Docker.User user, ExecutionContext ctx) { + Docker.User u = super.visitUser(user, ctx); + + String userName = extractText(u.getUser()); + if ("root".equals(userName) || "0".equals(userName)) { + return SearchResult.found(u, "Explicitly runs as root"); + } + + return u; + } + + private @Nullable String extractText(Docker.@Nullable Argument arg) { + if (arg == null) { + return null; + } + StringBuilder builder = new StringBuilder(); + for (Docker.ArgumentContent content : arg.getContents()) { + if (content instanceof Docker.Literal) { + builder.append(((Docker.Literal) content).getText()); + } else if (content instanceof Docker.EnvironmentVariable) { + return null; + } + } + return builder.toString(); + } + }; + } +} diff --git a/rewrite-docker/src/main/java/org/openrewrite/docker/search/FindUnpinnedBaseImages.java b/rewrite-docker/src/main/java/org/openrewrite/docker/search/FindUnpinnedBaseImages.java new file mode 100644 index 0000000000..f65aabb77b --- /dev/null +++ b/rewrite-docker/src/main/java/org/openrewrite/docker/search/FindUnpinnedBaseImages.java @@ -0,0 +1,96 @@ +/* + * Copyright 2026 the original author or authors. + *

+ * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * https://www.apache.org/licenses/LICENSE-2.0 + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.openrewrite.docker.search; + +import lombok.EqualsAndHashCode; +import lombok.Value; +import org.jspecify.annotations.Nullable; +import org.openrewrite.ExecutionContext; +import org.openrewrite.Recipe; +import org.openrewrite.TreeVisitor; +import org.openrewrite.docker.DockerIsoVisitor; +import org.openrewrite.docker.tree.Docker; +import org.openrewrite.marker.SearchResult; + +/** + * Finds FROM instructions that use unpinned base images (either no tag or the 'latest' tag). + * Images pinned by digest are considered acceptable. + */ +@Value +@EqualsAndHashCode(callSuper = false) +public class FindUnpinnedBaseImages extends Recipe { + + @Override + public String getDisplayName() { + return "Find unpinned base images"; + } + + @Override + public String getDescription() { + return "Finds FROM instructions that use unpinned base images. " + + "Images without an explicit tag default to 'latest', which is not reproducible. " + + "Images pinned by digest are considered acceptable."; + } + + @Override + public TreeVisitor getVisitor() { + return new DockerIsoVisitor() { + + @Override + public Docker.From visitFrom(Docker.From from, ExecutionContext ctx) { + Docker.From f = super.visitFrom(from, ctx); + + String imageName = extractText(f.getImageName()); + if (imageName == null) { + return f; // Contains environment variables + } + + // Skip "scratch" base image - it's a special case + if ("scratch".equals(imageName)) { + return f; + } + + String tag = extractText(f.getTag()); + String digest = extractText(f.getDigest()); + + // Check for latest tag or no tag (and no digest) + if (digest == null && (tag == null || "latest".equals(tag))) { + String message = tag == null ? + "Uses implicit 'latest' tag" : + "Uses 'latest' tag"; + return SearchResult.found(f, message); + } + + return f; + } + + private @Nullable String extractText(Docker.@Nullable Argument arg) { + if (arg == null) { + return null; + } + StringBuilder builder = new StringBuilder(); + for (Docker.ArgumentContent content : arg.getContents()) { + if (content instanceof Docker.Literal) { + builder.append(((Docker.Literal) content).getText()); + } else if (content instanceof Docker.EnvironmentVariable) { + return null; + } + } + return builder.toString(); + } + }; + } +} diff --git a/rewrite-docker/src/main/java/org/openrewrite/docker/search/package-info.java b/rewrite-docker/src/main/java/org/openrewrite/docker/search/package-info.java new file mode 100644 index 0000000000..f70861dbaf --- /dev/null +++ b/rewrite-docker/src/main/java/org/openrewrite/docker/search/package-info.java @@ -0,0 +1,21 @@ +/* + * Copyright 2026 the original author or authors. + *

+ * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * https://www.apache.org/licenses/LICENSE-2.0 + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +@NullMarked +@NonNullFields +package org.openrewrite.docker.search; + +import org.jspecify.annotations.NullMarked; +import org.openrewrite.internal.lang.NonNullFields; diff --git a/rewrite-docker/src/main/java/org/openrewrite/docker/table/DockerBaseImages.java b/rewrite-docker/src/main/java/org/openrewrite/docker/table/DockerBaseImages.java new file mode 100644 index 0000000000..1bdb1faa87 --- /dev/null +++ b/rewrite-docker/src/main/java/org/openrewrite/docker/table/DockerBaseImages.java @@ -0,0 +1,62 @@ +/* + * Copyright 2026 the original author or authors. + *

+ * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * https://www.apache.org/licenses/LICENSE-2.0 + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.openrewrite.docker.table; + +import lombok.Value; +import org.jspecify.annotations.Nullable; +import org.openrewrite.Column; +import org.openrewrite.DataTable; +import org.openrewrite.Recipe; + +public class DockerBaseImages extends DataTable { + + public DockerBaseImages(Recipe recipe) { + super(recipe, + "Docker base images", + "Records the base images found in Dockerfiles."); + } + + @Value + public static class Row { + @Column(displayName = "Source file", + description = "The Dockerfile containing the base image.") + String sourceFile; + + @Column(displayName = "Stage name", + description = "The build stage name (from AS clause), if specified.") + @Nullable + String stageName; + + @Column(displayName = "Image name", + description = "The name of the base image (without tag or digest).") + String imageName; + + @Column(displayName = "Tag", + description = "The image tag, if specified.") + @Nullable + String tag; + + @Column(displayName = "Digest", + description = "The image digest, if specified.") + @Nullable + String digest; + + @Column(displayName = "Platform", + description = "The platform flag value, if specified.") + @Nullable + String platform; + } +} diff --git a/rewrite-docker/src/main/java/org/openrewrite/docker/table/DockerExposedPorts.java b/rewrite-docker/src/main/java/org/openrewrite/docker/table/DockerExposedPorts.java new file mode 100644 index 0000000000..e95172c97c --- /dev/null +++ b/rewrite-docker/src/main/java/org/openrewrite/docker/table/DockerExposedPorts.java @@ -0,0 +1,52 @@ +/* + * Copyright 2026 the original author or authors. + *

+ * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * https://www.apache.org/licenses/LICENSE-2.0 + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.openrewrite.docker.table; + +import lombok.Value; +import org.jspecify.annotations.Nullable; +import org.openrewrite.Column; +import org.openrewrite.DataTable; +import org.openrewrite.Recipe; + +public class DockerExposedPorts extends DataTable { + + public DockerExposedPorts(Recipe recipe) { + super(recipe, + "Docker exposed ports", + "Records all ports exposed in EXPOSE instructions in Dockerfiles."); + } + + @Value + public static class Row { + @Column(displayName = "Source file", + description = "The Dockerfile containing the EXPOSE instruction.") + String sourceFile; + + @Column(displayName = "Stage name", + description = "The build stage name if the EXPOSE is in a named stage.") + @Nullable + String stageName; + + @Column(displayName = "Port", + description = "The port number or range (e.g., '80' or '8000-8100').") + String port; + + @Column(displayName = "Protocol", + description = "The protocol if specified (tcp or udp), null if not specified.") + @Nullable + String protocol; + } +} diff --git a/rewrite-docker/src/main/java/org/openrewrite/docker/table/package-info.java b/rewrite-docker/src/main/java/org/openrewrite/docker/table/package-info.java new file mode 100644 index 0000000000..cbf9cea1e3 --- /dev/null +++ b/rewrite-docker/src/main/java/org/openrewrite/docker/table/package-info.java @@ -0,0 +1,21 @@ +/* + * Copyright 2026 the original author or authors. + *

+ * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * https://www.apache.org/licenses/LICENSE-2.0 + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +@NullMarked +@NonNullFields +package org.openrewrite.docker.table; + +import org.jspecify.annotations.NullMarked; +import org.openrewrite.internal.lang.NonNullFields; diff --git a/rewrite-docker/src/main/java/org/openrewrite/docker/tree/Comment.java b/rewrite-docker/src/main/java/org/openrewrite/docker/tree/Comment.java new file mode 100644 index 0000000000..adcab93e6a --- /dev/null +++ b/rewrite-docker/src/main/java/org/openrewrite/docker/tree/Comment.java @@ -0,0 +1,40 @@ +/* + * Copyright 2026 the original author or authors. + *

+ * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * https://www.apache.org/licenses/LICENSE-2.0 + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.openrewrite.docker.tree; + +import lombok.Value; +import lombok.With; +import org.openrewrite.marker.Markers; + +/** + * Represents a comment in a Dockerfile. + * Comments in Dockerfile start with # and continue to the end of the line. + */ +@Value +@With +public class Comment { + /** + * The text of the comment, including the leading # + */ + String text; + + /** + * Whitespace that precedes this comment + */ + String prefix; + + Markers markers; +} diff --git a/rewrite-docker/src/main/java/org/openrewrite/docker/tree/Docker.java b/rewrite-docker/src/main/java/org/openrewrite/docker/tree/Docker.java new file mode 100644 index 0000000000..63477c26d9 --- /dev/null +++ b/rewrite-docker/src/main/java/org/openrewrite/docker/tree/Docker.java @@ -0,0 +1,1052 @@ +/* + * Copyright 2026 the original author or authors. + *

+ * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * https://www.apache.org/licenses/LICENSE-2.0 + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.openrewrite.docker.tree; + +import lombok.AccessLevel; +import lombok.EqualsAndHashCode; +import lombok.Value; +import lombok.With; +import org.jspecify.annotations.Nullable; +import org.openrewrite.*; +import org.openrewrite.docker.DockerVisitor; +import org.openrewrite.docker.internal.DockerPrinter; +import org.openrewrite.marker.Markers; + +import java.nio.charset.Charset; +import java.nio.file.Path; +import java.util.List; +import java.util.UUID; + +public interface Docker extends Tree { + + @SuppressWarnings("unchecked") + @Override + default R accept(TreeVisitor v, P p) { + return (R) acceptDocker(v.adapt(DockerVisitor.class), p); + } + + default

@Nullable Docker acceptDocker(DockerVisitor

v, P p) { + return v.defaultValue(this, p); + } + + @Override + default

boolean isAcceptable(TreeVisitor v, P p) { + return v.isAdaptableTo(DockerVisitor.class); + } + + Space getPrefix(); + + D withPrefix(Space prefix); + + /** + * Root node representing a complete Dockerfile or Containerfile + */ + @Value + @EqualsAndHashCode(callSuper = false, onlyExplicitlyIncluded = true) + @With + class File implements Docker, SourceFile { + @EqualsAndHashCode.Include + UUID id; + + Path sourcePath; + Space prefix; + Markers markers; + + @With(AccessLevel.PRIVATE) + String charsetName; + + boolean charsetBomMarked; + + @Nullable + Checksum checksum; + + @Nullable + FileAttributes fileAttributes; + + @Override + public Charset getCharset() { + return Charset.forName(charsetName); + } + + @Override + @SuppressWarnings("unchecked") + public File withCharset(Charset charset) { + return withCharsetName(charset.name()); + } + + /** + * Global ARG instructions that appear before the first FROM instruction. + * These have global scope and can be referenced in FROM instructions. + */ + List globalArgs; + + /** + * Build stages, each starting with a FROM instruction. + * Even single-stage Dockerfiles will have one Stage. + */ + List stages; + + Space eof; + + @Override + public

Docker acceptDocker(DockerVisitor

v, P p) { + return v.visitFile(this, p); + } + + @Override + public

TreeVisitor> printer(Cursor cursor) { + return new DockerPrinter<>(); + } + } + + /** + * A build stage in a multi-stage Dockerfile. + * Each stage begins with a FROM instruction and contains subsequent instructions + * until the next FROM or end of file. + */ + @Value + @EqualsAndHashCode(callSuper = false, onlyExplicitlyIncluded = true) + @With + class Stage implements Docker { + @EqualsAndHashCode.Include + UUID id; + + Space prefix; + Markers markers; + + /** + * The FROM instruction that starts this stage + */ + From from; + + /** + * Instructions in this stage (RUN, COPY, etc.) + * Does not include the FROM instruction or global ARGs + */ + List instructions; + + @Override + public

Docker acceptDocker(DockerVisitor

v, P p) { + return v.visitStage(this, p); + } + } + + /** + * Base interface for all Dockerfile instructions + */ + interface Instruction extends Docker { + } + + /** + * FROM instruction - sets the base image + */ + @Value + @EqualsAndHashCode(callSuper = false, onlyExplicitlyIncluded = true) + @With + class From implements Instruction { + @EqualsAndHashCode.Include + UUID id; + + Space prefix; + Markers markers; + + String keyword; + + @Nullable + List flags; + + Argument imageName; + + @Nullable + Argument tag; + + @Nullable + Argument digest; + + @Nullable + As as; + + @Override + public

Docker acceptDocker(DockerVisitor

v, P p) { + return v.visitFrom(this, p); + } + + @Value + @EqualsAndHashCode(callSuper = false, onlyExplicitlyIncluded = true) + @With + public static class As { + @EqualsAndHashCode.Include + UUID id; + + Space prefix; + Markers markers; + String keyword; + Literal name; + } + } + + /** + * RUN instruction - executes commands + */ + @Value + @EqualsAndHashCode(callSuper = false, onlyExplicitlyIncluded = true) + @With + class Run implements Instruction { + @EqualsAndHashCode.Include + UUID id; + + Space prefix; + Markers markers; + + String keyword; + + @Nullable + List flags; + + CommandForm command; + + @Override + public

Docker acceptDocker(DockerVisitor

v, P p) { + return v.visitRun(this, p); + } + } + + /** + * ADD instruction - adds files from source to destination (can extract archives and fetch URLs) + */ + @Value + @EqualsAndHashCode(callSuper = false, onlyExplicitlyIncluded = true) + @With + class Add implements Instruction { + @EqualsAndHashCode.Include + UUID id; + + Space prefix; + Markers markers; + + String keyword; + + @Nullable + List flags; + + /** + * The form of this ADD instruction: CopyShellForm, ExecForm, or HeredocForm. + */ + CopyAddForm form; + + /** + * Returns the form as a CopyShellForm, or null if this instruction uses a different form. + */ + public @Nullable CopyShellForm getShellForm() { + return form instanceof CopyShellForm ? (CopyShellForm) form : null; + } + + /** + * Returns the form as an ExecForm, or null if this instruction uses a different form. + */ + public @Nullable ExecForm getExecForm() { + return form instanceof ExecForm ? (ExecForm) form : null; + } + + /** + * Returns the form as a HeredocForm, or null if this instruction uses a different form. + */ + public @Nullable HeredocForm getHeredoc() { + return form instanceof HeredocForm ? (HeredocForm) form : null; + } + + @Override + public

Docker acceptDocker(DockerVisitor

v, P p) { + return v.visitAdd(this, p); + } + } + + /** + * COPY instruction - copies files from source to destination + */ + @Value + @EqualsAndHashCode(callSuper = false, onlyExplicitlyIncluded = true) + @With + class Copy implements Instruction { + @EqualsAndHashCode.Include + UUID id; + + Space prefix; + Markers markers; + + String keyword; + + @Nullable + List flags; + + /** + * The form of this COPY instruction: CopyShellForm, ExecForm, or HeredocForm. + */ + CopyAddForm form; + + /** + * Returns the form as a CopyShellForm, or null if this instruction uses a different form. + */ + public @Nullable CopyShellForm getShellForm() { + return form instanceof CopyShellForm ? (CopyShellForm) form : null; + } + + /** + * Returns the form as an ExecForm, or null if this instruction uses a different form. + */ + public @Nullable ExecForm getExecForm() { + return form instanceof ExecForm ? (ExecForm) form : null; + } + + /** + * Returns the form as a HeredocForm, or null if this instruction uses a different form. + */ + public @Nullable HeredocForm getHeredoc() { + return form instanceof HeredocForm ? (HeredocForm) form : null; + } + + @Override + public

Docker acceptDocker(DockerVisitor

v, P p) { + return v.visitCopy(this, p); + } + } + + /** + * ARG instruction - defines a build argument + */ + @Value + @EqualsAndHashCode(callSuper = false, onlyExplicitlyIncluded = true) + @With + class Arg implements Instruction { + @EqualsAndHashCode.Include + UUID id; + + Space prefix; + Markers markers; + + String keyword; + + Literal name; + + @Nullable + Argument value; + + @Override + public

Docker acceptDocker(DockerVisitor

v, P p) { + return v.visitArg(this, p); + } + } + + /** + * ENV instruction - sets environment variables + */ + @Value + @EqualsAndHashCode(callSuper = false, onlyExplicitlyIncluded = true) + @With + class Env implements Instruction { + @EqualsAndHashCode.Include + UUID id; + + Space prefix; + Markers markers; + + String keyword; + + List pairs; + + @Override + public

Docker acceptDocker(DockerVisitor

v, P p) { + return v.visitEnv(this, p); + } + + @Value + @EqualsAndHashCode(callSuper = false, onlyExplicitlyIncluded = true) + @With + public static class EnvPair { + @EqualsAndHashCode.Include + UUID id; + + Space prefix; + Markers markers; + Literal key; + boolean hasEquals; // true for KEY=value, false for KEY value (old format) + Argument value; + } + } + + /** + * LABEL instruction - adds metadata to an image + */ + @Value + @EqualsAndHashCode(callSuper = false, onlyExplicitlyIncluded = true) + @With + class Label implements Instruction { + @EqualsAndHashCode.Include + UUID id; + + Space prefix; + Markers markers; + + String keyword; + + List pairs; + + @Override + public

Docker acceptDocker(DockerVisitor

v, P p) { + return v.visitLabel(this, p); + } + + @Value + @EqualsAndHashCode(callSuper = false, onlyExplicitlyIncluded = true) + @With + public static class LabelPair { + @EqualsAndHashCode.Include + UUID id; + + Space prefix; + Markers markers; + Argument key; + boolean hasEquals; // true for key=value, false for old format "key value" + Argument value; + } + } + + /** + * CMD instruction - provides defaults for executing container + */ + @Value + @EqualsAndHashCode(callSuper = false, onlyExplicitlyIncluded = true) + @With + class Cmd implements Instruction { + @EqualsAndHashCode.Include + UUID id; + + Space prefix; + Markers markers; + + String keyword; + + CommandForm command; + + @Override + public

Docker acceptDocker(DockerVisitor

v, P p) { + return v.visitCmd(this, p); + } + } + + /** + * ENTRYPOINT instruction - configures container to run as executable + */ + @Value + @EqualsAndHashCode(callSuper = false, onlyExplicitlyIncluded = true) + @With + class Entrypoint implements Instruction { + @EqualsAndHashCode.Include + UUID id; + + Space prefix; + Markers markers; + + String keyword; + + CommandForm command; + + @Override + public

Docker acceptDocker(DockerVisitor

v, P p) { + return v.visitEntrypoint(this, p); + } + } + + /** + * EXPOSE instruction - documents ports that the container listens on + */ + @Value + @EqualsAndHashCode(callSuper = false, onlyExplicitlyIncluded = true) + @With + class Expose implements Instruction { + @EqualsAndHashCode.Include + UUID id; + + Space prefix; + Markers markers; + + String keyword; + + List ports; + + @Override + public

Docker acceptDocker(DockerVisitor

v, P p) { + return v.visitExpose(this, p); + } + } + + /** + * A port specification in an EXPOSE instruction. + * Supports formats: 80, 80/tcp, 80/udp, 8000-9000, 8000-9000/tcp, ${PORT} + */ + @Value + @EqualsAndHashCode(callSuper = false, onlyExplicitlyIncluded = true) + @With + class Port implements Docker { + @EqualsAndHashCode.Include + UUID id; + + Space prefix; + Markers markers; + + /** + * The raw port specification text (for lossless printing and env vars) + */ + String text; + + /** + * The starting port number, or null if the port is an environment variable + */ + @Nullable + Integer start; + + /** + * The ending port number for ranges (e.g., 8000-9000), or null if not a range + */ + @Nullable + Integer end; + + /** + * The protocol (TCP or UDP). Defaults to TCP if not specified. + */ + Protocol protocol; + + public enum Protocol { + TCP, UDP + } + + @Override + public

Docker acceptDocker(DockerVisitor

v, P p) { + return v.visitPort(this, p); + } + + /** + * Returns true if this port specification contains environment variables + * and the actual port number is not known at parse time. + */ + public boolean isVariable() { + return start == null; + } + + /** + * Returns true if this is a port range (e.g., 8000-9000) + */ + public boolean isRange() { + return end != null; + } + } + + /** + * VOLUME instruction - creates a mount point + */ + @Value + @EqualsAndHashCode(callSuper = false, onlyExplicitlyIncluded = true) + @With + class Volume implements Instruction { + @EqualsAndHashCode.Include + UUID id; + + Space prefix; + Markers markers; + + String keyword; + + boolean jsonForm; // true for ["path1", "path2"], false for path1 path2 + + /** + * Whitespace before the opening bracket in JSON form (to preserve "VOLUME [" vs "VOLUME [") + */ + Space openingBracketPrefix; + + List values; + + /** + * Whitespace before the closing bracket in JSON form (to preserve " ]" vs "]") + */ + Space closingBracketPrefix; + + @Override + public

Docker acceptDocker(DockerVisitor

v, P p) { + return v.visitVolume(this, p); + } + } + + /** + * SHELL instruction - sets the default shell + */ + @Value + @EqualsAndHashCode(callSuper = false, onlyExplicitlyIncluded = true) + @With + class Shell implements Instruction { + @EqualsAndHashCode.Include + UUID id; + + Space prefix; + Markers markers; + + String keyword; + + /** + * Whitespace before the opening bracket (to preserve "SHELL [" vs "SHELL [" vs "SHELL\t[") + */ + Space openingBracketPrefix; + + List arguments; // JSON array elements + + /** + * Whitespace before the closing bracket (to preserve " ]" vs "]") + */ + Space closingBracketPrefix; + + @Override + public

Docker acceptDocker(DockerVisitor

v, P p) { + return v.visitShell(this, p); + } + } + + /** + * WORKDIR instruction - sets the working directory + */ + @Value + @EqualsAndHashCode(callSuper = false, onlyExplicitlyIncluded = true) + @With + class Workdir implements Instruction { + @EqualsAndHashCode.Include + UUID id; + + Space prefix; + Markers markers; + + String keyword; + + Argument path; + + @Override + public

Docker acceptDocker(DockerVisitor

v, P p) { + return v.visitWorkdir(this, p); + } + } + + /** + * USER instruction - sets the user and optionally group + */ + @Value + @EqualsAndHashCode(callSuper = false, onlyExplicitlyIncluded = true) + @With + class User implements Instruction { + @EqualsAndHashCode.Include + UUID id; + + Space prefix; + Markers markers; + + String keyword; + + Argument user; + + @Nullable + Argument group; + + @Override + public

Docker acceptDocker(DockerVisitor

v, P p) { + return v.visitUser(this, p); + } + } + + /** + * STOPSIGNAL instruction - sets the signal to stop the container + */ + @Value + @EqualsAndHashCode(callSuper = false, onlyExplicitlyIncluded = true) + @With + class Stopsignal implements Instruction { + @EqualsAndHashCode.Include + UUID id; + + Space prefix; + Markers markers; + + String keyword; + + Argument signal; + + @Override + public

Docker acceptDocker(DockerVisitor

v, P p) { + return v.visitStopsignal(this, p); + } + } + + /** + * ONBUILD instruction - prefixes another instruction to be executed later + */ + @Value + @EqualsAndHashCode(callSuper = false, onlyExplicitlyIncluded = true) + @With + class Onbuild implements Instruction { + @EqualsAndHashCode.Include + UUID id; + + Space prefix; + Markers markers; + + String keyword; + + Instruction instruction; + + @Override + public

Docker acceptDocker(DockerVisitor

v, P p) { + return v.visitOnbuild(this, p); + } + } + + /** + * HEALTHCHECK instruction - tells Docker how to test container health + */ + @Value + @EqualsAndHashCode(callSuper = false, onlyExplicitlyIncluded = true) + @With + class Healthcheck implements Instruction { + @EqualsAndHashCode.Include + UUID id; + + Space prefix; + Markers markers; + + String keyword; + + @Nullable + List flags; + + /** + * Whitespace before NONE when isNone is true (to preserve "HEALTHCHECK NONE" vs "HEALTHCHECK NONE") + */ + Space nonePrefix; + + @Nullable + Cmd cmd; // null when isNone is true + + @Override + public

Docker acceptDocker(DockerVisitor

v, P p) { + return v.visitHealthcheck(this, p); + } + + public boolean isNone() { + return cmd == null; + } + } + + /** + * MAINTAINER instruction - sets the author field (deprecated but still supported) + */ + @Value + @EqualsAndHashCode(callSuper = false, onlyExplicitlyIncluded = true) + @With + class Maintainer implements Instruction { + @EqualsAndHashCode.Include + UUID id; + + Space prefix; + Markers markers; + + String keyword; + + Argument text; + + @Override + public

Docker acceptDocker(DockerVisitor

v, P p) { + return v.visitMaintainer(this, p); + } + } + + /** + * Base for command forms (used by RUN, CMD, ENTRYPOINT) + */ + interface CommandForm extends Docker { + } + + /** + * Base for COPY/ADD forms (shell form, exec form, or heredoc) + */ + interface CopyAddForm extends Docker { + } + + /** + * Shell form: CMD command param1 param2 + */ + @Value + @EqualsAndHashCode(callSuper = false, onlyExplicitlyIncluded = true) + @With + class ShellForm implements CommandForm { + @EqualsAndHashCode.Include + UUID id; + + Space prefix; + Markers markers; + + Literal argument; + + @Override + public

Docker acceptDocker(DockerVisitor

v, P p) { + return v.visitShellForm(this, p); + } + } + + /** + * Exec form: CMD ["executable", "param1", "param2"] + * Also used for COPY/ADD JSON array form. + */ + @Value + @EqualsAndHashCode(callSuper = false, onlyExplicitlyIncluded = true) + @With + class ExecForm implements CommandForm, CopyAddForm { + @EqualsAndHashCode.Include + UUID id; + + Space prefix; + Markers markers; + + List arguments; + + /** + * Whitespace before the closing bracket (to preserve " ]" vs "]") + */ + Space closingBracketPrefix; + + @Override + public

Docker acceptDocker(DockerVisitor

v, P p) { + return v.visitExecForm(this, p); + } + } + + /** + * Represents a single heredoc body within a heredoc command. + * Each body has its own opening marker, content lines, and closing marker. + */ + @Value + @EqualsAndHashCode(callSuper = false, onlyExplicitlyIncluded = true) + @With + class HeredocBody implements Docker { + @EqualsAndHashCode.Include + UUID id; + + Space prefix; + Markers markers; + + /** + * The opening marker including << and optional dash (e.g., "< contentLines; + + /** + * The closing marker (e.g., "EOF") + */ + String closing; + + @Override + public

Docker acceptDocker(DockerVisitor

v, P p) { + return v.visitHeredocBody(this, p); + } + } + + /** + * Unified heredoc form supporting both single and multiple heredocs. + *

+ * Single heredoc example: RUN <<EOF\ncommands\nEOF + * Multi-heredoc example: RUN <<EOF1 cat >file1.sh &&\ <<EOF2 cat >file2.sh + *

+ * Consists of a shell preamble (containing heredoc markers and optional shell commands) + * followed by ordered heredoc bodies. + * Also used for COPY/ADD heredoc form. + */ + @Value + @EqualsAndHashCode(callSuper = false, onlyExplicitlyIncluded = true) + @With + class HeredocForm implements CommandForm, CopyAddForm { + @EqualsAndHashCode.Include + UUID id; + + Space prefix; + Markers markers; + + /** + * The shell command preamble containing heredoc marker(s) and optional shell commands. + */ + String preamble; + + /** + * Optional destination path (for COPY/ADD with inline destination). + */ + @Nullable + Argument destination; + + /** + * Ordered list of heredoc bodies, matching the order of markers in the preamble. + */ + List bodies; + + @Override + public

Docker acceptDocker(DockerVisitor

v, P p) { + return v.visitHeredocForm(this, p); + } + } + + /** + * Shell form for COPY/ADD instructions: sources followed by destination. + * Example: COPY file1.txt file2.txt /app/ + */ + @Value + @EqualsAndHashCode(callSuper = false, onlyExplicitlyIncluded = true) + @With + class CopyShellForm implements CopyAddForm { + @EqualsAndHashCode.Include + UUID id; + + Space prefix; + Markers markers; + + List sources; + Argument destination; + + @Override + public

Docker acceptDocker(DockerVisitor

v, P p) { + return v.visitCopyShellForm(this, p); + } + } + + /** + * A flag like --platform=linux/amd64 + */ + @Value + @EqualsAndHashCode(callSuper = false, onlyExplicitlyIncluded = true) + @With + class Flag implements Docker { + @EqualsAndHashCode.Include + UUID id; + + Space prefix; + Markers markers; + + String name; + + @Nullable + Argument value; + + @Override + public

Docker acceptDocker(DockerVisitor

v, P p) { + return v.visitFlag(this, p); + } + } + + /** + * An argument which can be plain text, quoted string, or environment variable + */ + @Value + @EqualsAndHashCode(callSuper = false, onlyExplicitlyIncluded = true) + @With + class Argument implements Docker { + @EqualsAndHashCode.Include + UUID id; + + Space prefix; + Markers markers; + + List contents; + + @Override + public

Docker acceptDocker(DockerVisitor

v, P p) { + return v.visitArgument(this, p); + } + } + + /** + * Content within an argument (text, quoted string, or variable reference) + */ + interface ArgumentContent extends Docker { + } + + /** + * A literal text value, either plain (unquoted) or quoted (single/double quotes). + */ + @Value + @EqualsAndHashCode(callSuper = false, onlyExplicitlyIncluded = true) + @With + class Literal implements ArgumentContent { + @EqualsAndHashCode.Include + UUID id; + + Space prefix; + Markers markers; + + /** + * The text value (without surrounding quotes if quoted) + */ + String text; + + /** + * The quote style, or null for plain unquoted text + */ + @Nullable + QuoteStyle quoteStyle; + + public enum QuoteStyle { + DOUBLE, SINGLE + } + + /** + * Returns true if this is a quoted string + */ + public boolean isQuoted() { + return quoteStyle != null; + } + + @Override + public

Docker acceptDocker(DockerVisitor

v, P p) { + return v.visitLiteral(this, p); + } + } + + /** + * Environment variable reference like $VAR or ${VAR} + */ + @Value + @EqualsAndHashCode(callSuper = false, onlyExplicitlyIncluded = true) + @With + class EnvironmentVariable implements ArgumentContent { + @EqualsAndHashCode.Include + UUID id; + + Space prefix; + Markers markers; + + String name; + boolean braced; + + @Override + public

Docker acceptDocker(DockerVisitor

v, P p) { + return v.visitEnvironmentVariable(this, p); + } + } +} diff --git a/rewrite-docker/src/main/java/org/openrewrite/docker/tree/Space.java b/rewrite-docker/src/main/java/org/openrewrite/docker/tree/Space.java new file mode 100644 index 0000000000..97e003e772 --- /dev/null +++ b/rewrite-docker/src/main/java/org/openrewrite/docker/tree/Space.java @@ -0,0 +1,229 @@ +/* + * Copyright 2026 the original author or authors. + *

+ * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * https://www.apache.org/licenses/LICENSE-2.0 + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.openrewrite.docker.tree; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonIdentityInfo; +import com.fasterxml.jackson.annotation.ObjectIdGenerators; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import org.jspecify.annotations.Nullable; +import org.openrewrite.marker.Markers; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.WeakHashMap; + +import static java.util.Collections.emptyList; +import static java.util.Collections.synchronizedMap; + +/** + * Whitespace and comments in Dockerfile. + * Comments in Dockerfile start with # and continue to the end of the line. + */ +@EqualsAndHashCode +@JsonIdentityInfo(generator = ObjectIdGenerators.IntSequenceGenerator.class, property = "@ref") +public class Space { + public static final Space EMPTY = new Space("", emptyList()); + public static final Space SINGLE_SPACE = new Space(" ", emptyList()); + + @Getter + private final List comments; + + @Nullable + private final String whitespace; + + /* + * Most occurrences of spaces will have no comments and will be repeated frequently throughout a source file. + * Use flyweights to avoid storing many instances of functionally identical spaces + */ + private static final Map flyweights = synchronizedMap(new WeakHashMap<>()); + + private Space(@Nullable String whitespace, List comments) { + this.comments = comments; + this.whitespace = whitespace == null || whitespace.isEmpty() ? null : whitespace; + } + + @JsonCreator + public static Space build(@Nullable String whitespace, List comments) { + if (comments.isEmpty()) { + if (whitespace == null || whitespace.isEmpty()) { + return Space.EMPTY; + } + if (whitespace.length() <= 100) { + //noinspection StringOperationCanBeSimplified + return flyweights.computeIfAbsent(whitespace, k -> new Space(new String(whitespace), comments)); + } + } + return new Space(whitespace, comments); + } + + public String getIndent() { + if (!comments.isEmpty()) { + return getWhitespaceIndent(comments.get(comments.size() - 1).getPrefix()); + } + return getWhitespaceIndent(whitespace); + } + + public String getLastWhitespace() { + if (!comments.isEmpty()) { + return comments.get(comments.size() - 1).getPrefix(); + } + return whitespace == null ? "" : whitespace; + } + + private String getWhitespaceIndent(@Nullable String whitespace) { + if (whitespace == null) { + return ""; + } + int lastNewline = whitespace.lastIndexOf('\n'); + if (lastNewline >= 0) { + return whitespace.substring(lastNewline + 1); + } + if (lastNewline == whitespace.length() - 1) { + return ""; + } + return whitespace; + } + + public String getWhitespace() { + return whitespace == null ? "" : whitespace; + } + + public boolean hasComment(String comment) { + for (Comment c : comments) { + if (c.getText().equals(comment)) { + return true; + } + } + return false; + } + + public Space withComments(List comments) { + if (comments == this.comments) { + return this; + } + if (comments.isEmpty() && (whitespace == null || whitespace.isEmpty())) { + return Space.EMPTY; + } + return build(whitespace, comments); + } + + public Space withWhitespace(String whitespace) { + if (comments.isEmpty() && whitespace.isEmpty()) { + return Space.EMPTY; + } + if ((whitespace.isEmpty() && this.whitespace == null) || whitespace.equals(this.whitespace)) { + return this; + } + return build(whitespace, comments); + } + + public boolean isEmpty() { + return this == EMPTY; + } + + public static Space firstPrefix(@Nullable List trees) { + return trees == null || trees.isEmpty() ? Space.EMPTY : trees.iterator().next().getPrefix(); + } + + public static Space format(String formatting) { + return format(formatting, 0, formatting.length()); + } + + public static Space format(String formatting, int beginIndex, int toIndex) { + if (beginIndex == toIndex) { + return Space.EMPTY; + } + if (toIndex == beginIndex + 1 && ' ' == formatting.charAt(beginIndex)) { + return Space.SINGLE_SPACE; + } + rangeCheck(formatting.length(), beginIndex, toIndex); + + StringBuilder prefix = new StringBuilder(); + StringBuilder comment = new StringBuilder(); + List comments = new ArrayList<>(1); + + boolean inComment = false; + + for (int i = beginIndex; i < toIndex; i++) { + char c = formatting.charAt(i); + switch (c) { + case '#': + if (inComment) { + comment.append(c); + } + else { + inComment = true; + comment.setLength(0); + comment.append(c); + } + break; + case '\r': + case '\n': + if (inComment) { + inComment = false; + comments.add(new Comment(comment.toString(), prefix.toString(), Markers.EMPTY)); + prefix.setLength(0); + comment.setLength(0); + prefix.append(c); + } + else { + prefix.append(c); + } + break; + default: + if (inComment) { + comment.append(c); + } + else { + prefix.append(c); + } + } + } + // If a file ends with a comment there may be no terminating newline + if (comment.length() > 0 || inComment) { + comments.add(new Comment(comment.toString(), prefix.toString(), Markers.EMPTY)); + prefix.setLength(0); + } + + return build(prefix.toString(), comments); + } + + private static void rangeCheck(int arrayLength, int fromIndex, int toIndex) { + if (fromIndex > toIndex) { + throw new IllegalArgumentException("fromIndex(" + fromIndex + ") > toIndex(" + toIndex + ")"); + } + if (fromIndex < 0) { + throw new ArrayIndexOutOfBoundsException(fromIndex); + } + if (toIndex > arrayLength) { + throw new ArrayIndexOutOfBoundsException(toIndex); + } + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + for (Comment comment : comments) { + sb.append(comment.getPrefix()); + sb.append(comment.getText()); + } + sb.append(whitespace == null ? "" : whitespace); + return sb.toString(); + } +} diff --git a/rewrite-docker/src/main/java/org/openrewrite/docker/tree/package-info.java b/rewrite-docker/src/main/java/org/openrewrite/docker/tree/package-info.java new file mode 100644 index 0000000000..3a3571f501 --- /dev/null +++ b/rewrite-docker/src/main/java/org/openrewrite/docker/tree/package-info.java @@ -0,0 +1,19 @@ +/* + * Copyright 2026 the original author or authors. + *

+ * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * https://www.apache.org/licenses/LICENSE-2.0 + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +@NullMarked +package org.openrewrite.docker.tree; + +import org.jspecify.annotations.NullMarked; diff --git a/rewrite-docker/src/main/resources/META-INF/rewrite/category.yml b/rewrite-docker/src/main/resources/META-INF/rewrite/category.yml new file mode 100644 index 0000000000..0638260083 --- /dev/null +++ b/rewrite-docker/src/main/resources/META-INF/rewrite/category.yml @@ -0,0 +1,22 @@ +# +# Copyright 2026 the original author or authors. +#

+# Licensed under the Moderne Source Available License (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +#

+# https://docs.moderne.io/licensing/moderne-source-available-license +#

+# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +--- +type: specs.openrewrite.org/v1beta/category +name: Docker +packageName: org.openrewrite.docker +description: Recipes to perform [Docker](https://docker.com/) migration tasks. +--- diff --git a/rewrite-docker/src/main/resources/META-INF/rewrite/recipes.csv b/rewrite-docker/src/main/resources/META-INF/rewrite/recipes.csv new file mode 100644 index 0000000000..8f76521ead --- /dev/null +++ b/rewrite-docker/src/main/resources/META-INF/rewrite/recipes.csv @@ -0,0 +1,8 @@ +ecosystem,packageName,name,displayName,description,recipeCount,category1,category2,category1Description,category2Description,options,dataTables +maven,org.openrewrite:rewrite-docker,org.openrewrite.docker.AddOrUpdateLabel,Add Docker LABEL instruction,"Adds or updates a LABEL instruction in a Dockerfile. By default, adds to the final stage only.",1,,Docker,,Recipes to perform [Docker](https://docker.com/) migration tasks.,"[{""name"":""key"",""type"":""String"",""displayName"":""Label key"",""description"":""The key of the label to add."",""example"":""org.opencontainers.image.version"",""required"":true},{""name"":""value"",""type"":""String"",""displayName"":""Label value"",""description"":""The value of the label."",""example"":""1.0.0"",""required"":true},{""name"":""overwriteExisting"",""type"":""Boolean"",""displayName"":""Overwrite existing"",""description"":""If true, overwrite the label if it already exists. If false, skip if exists. Defaults to true.""},{""name"":""stageName"",""type"":""String"",""displayName"":""Stage name"",""description"":""Only add the label to this build stage. If null, adds to the final stage only."",""example"":""final""}]", +maven,org.openrewrite:rewrite-docker,org.openrewrite.docker.ChangeBaseImage,Change Docker base image,Change the base image in a Dockerfile FROM instruction.,1,,Docker,,Recipes to perform [Docker](https://docker.com/) migration tasks.,"[{""name"":""oldImageName"",""type"":""String"",""displayName"":""Old image name"",""description"":""The old image name to replace. Supports glob patterns."",""example"":""ubuntu:20.04"",""required"":true},{""name"":""newImageName"",""type"":""String"",""displayName"":""New image name"",""description"":""The new image name to use."",""example"":""ubuntu:22.04"",""required"":true},{""name"":""oldPlatform"",""type"":""String"",""displayName"":""Old platform"",""description"":""Only change images with this platform. If null, matches any platform."",""example"":""linux/amd64""},{""name"":""newPlatform"",""type"":""String"",""displayName"":""New platform"",""description"":""Set the platform to this value. If null and oldPlatform is specified, removes the platform flag from matched images. If both oldPlatform and newPlatform are null, platform flags are preserved."",""example"":""linux/arm64""}]", +maven,org.openrewrite:rewrite-docker,org.openrewrite.docker.search.FindDockerBaseImages,Find Docker base images,Find all base images (FROM instructions) in Dockerfiles.,1,Search,Docker,,Recipes to perform [Docker](https://docker.com/) migration tasks.,"[{""name"":""imageNamePattern"",""type"":""String"",""displayName"":""Image name pattern"",""description"":""A glob pattern to match against base image names. If not specified, all base images are matched."",""example"":""ubuntu*""}]","[{""name"":""org.openrewrite.docker.table.DockerBaseImages"",""displayName"":""Docker base images"",""description"":""Records the base images found in Dockerfiles."",""columns"":[{""name"":""sourceFile"",""type"":""String"",""displayName"":""Source file"",""description"":""The Dockerfile containing the base image.""},{""name"":""stageName"",""type"":""String"",""displayName"":""Stage name"",""description"":""The build stage name (from AS clause), if specified.""},{""name"":""imageName"",""type"":""String"",""displayName"":""Image name"",""description"":""The name of the base image (without tag or digest).""},{""name"":""tag"",""type"":""String"",""displayName"":""Tag"",""description"":""The image tag, if specified.""},{""name"":""digest"",""type"":""String"",""displayName"":""Digest"",""description"":""The image digest, if specified.""},{""name"":""platform"",""type"":""String"",""displayName"":""Platform"",""description"":""The platform flag value, if specified.""}]}]" +maven,org.openrewrite:rewrite-docker,org.openrewrite.docker.search.FindExposedPorts,Find exposed ports,Find all EXPOSE instructions in Dockerfiles and report the exposed ports.,1,Search,Docker,,Recipes to perform [Docker](https://docker.com/) migration tasks.,"[{""name"":""portPattern"",""type"":""String"",""displayName"":""Port pattern"",""description"":""A glob pattern to filter ports. For example, '80*' to find ports starting with 80. If not specified, all EXPOSE instructions are matched."",""example"":""80*""}]","[{""name"":""org.openrewrite.docker.table.DockerExposedPorts"",""displayName"":""Docker exposed ports"",""description"":""Records all ports exposed in EXPOSE instructions in Dockerfiles."",""columns"":[{""name"":""sourceFile"",""type"":""String"",""displayName"":""Source file"",""description"":""The Dockerfile containing the EXPOSE instruction.""},{""name"":""stageName"",""type"":""String"",""displayName"":""Stage name"",""description"":""The build stage name if the EXPOSE is in a named stage.""},{""name"":""port"",""type"":""String"",""displayName"":""Port"",""description"":""The port number or range (e.g., '80' or '8000-8100').""},{""name"":""protocol"",""type"":""String"",""displayName"":""Protocol"",""description"":""The protocol if specified (tcp or udp), null if not specified.""}]}]" +maven,org.openrewrite:rewrite-docker,org.openrewrite.docker.search.FindMissingHealthcheck,Find missing HEALTHCHECK,Finds Dockerfiles where the final stage is missing a HEALTHCHECK instruction. Health checks help container orchestrators determine if a container is healthy and ready to receive traffic.,1,Search,Docker,,Recipes to perform [Docker](https://docker.com/) migration tasks.,, +maven,org.openrewrite:rewrite-docker,org.openrewrite.docker.search.FindRootUser,Find containers running as root,"Finds containers that run as root user. This includes explicit `USER root` or `USER 0` instructions, and optionally containers with no USER instruction in the final stage (which default to root).",1,Search,Docker,,Recipes to perform [Docker](https://docker.com/) migration tasks.,"[{""name"":""includeMissingUser"",""type"":""Boolean"",""displayName"":""Include missing USER"",""description"":""When true, also marks the final stage if no USER instruction is present (defaults to root). When false, only marks explicit USER root/0 instructions.""}]", +maven,org.openrewrite:rewrite-docker,org.openrewrite.docker.search.FindUnpinnedBaseImages,Find unpinned base images,"Finds FROM instructions that use unpinned base images. Images without an explicit tag default to 'latest', which is not reproducible. Images pinned by digest are considered acceptable.",1,Search,Docker,,Recipes to perform [Docker](https://docker.com/) migration tasks.,, diff --git a/rewrite-docker/src/test/java/.editorconfig b/rewrite-docker/src/test/java/.editorconfig new file mode 100644 index 0000000000..a4824935e4 --- /dev/null +++ b/rewrite-docker/src/test/java/.editorconfig @@ -0,0 +1,5 @@ +root = true + +[*.java] +indent_size = 4 +ij_continuation_indent_size = 2 diff --git a/rewrite-docker/src/test/java/org/openrewrite/docker/AddOrUpdateLabelTest.java b/rewrite-docker/src/test/java/org/openrewrite/docker/AddOrUpdateLabelTest.java new file mode 100644 index 0000000000..f4df3f9a9a --- /dev/null +++ b/rewrite-docker/src/test/java/org/openrewrite/docker/AddOrUpdateLabelTest.java @@ -0,0 +1,217 @@ +/* + * Copyright 2026 the original author or authors. + *

+ * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * https://www.apache.org/licenses/LICENSE-2.0 + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.openrewrite.docker; + +import org.junit.jupiter.api.Test; +import org.openrewrite.DocumentExample; +import org.openrewrite.test.RecipeSpec; +import org.openrewrite.test.RewriteTest; + +import static org.openrewrite.docker.Assertions.docker; + +class AddOrUpdateLabelTest implements RewriteTest { + + @Override + public void defaults(RecipeSpec spec) { + spec.recipe(new AddOrUpdateLabel("version", "1.0.0", null, null)); + } + + @DocumentExample + @Test + void addNewLabel() { + rewriteRun( + docker( + """ + FROM ubuntu:22.04 + RUN apt-get update + """, + """ + FROM ubuntu:22.04 + LABEL version=1.0.0 + RUN apt-get update + """ + ) + ); + } + + @Test + void addLabelWithQuotedValue() { + rewriteRun( + spec -> spec.recipe(new AddOrUpdateLabel("description", "My awesome app", null, null)), + docker( + """ + FROM ubuntu:22.04 + RUN apt-get update + """, + """ + FROM ubuntu:22.04 + LABEL description="My awesome app" + RUN apt-get update + """ + ) + ); + } + + @Test + void updateExistingLabel() { + rewriteRun( + docker( + """ + FROM ubuntu:22.04 + LABEL version=0.9.0 + RUN apt-get update + """, + """ + FROM ubuntu:22.04 + LABEL version=1.0.0 + RUN apt-get update + """ + ) + ); + } + + @Test + void skipExistingLabelWhenOverwriteFalse() { + rewriteRun( + spec -> spec.recipe(new AddOrUpdateLabel("version", "1.0.0", false, null)), + docker( + """ + FROM ubuntu:22.04 + LABEL version=0.9.0 + RUN apt-get update + """ + ) + ); + } + + @Test + void updateLabelWithSeparateInstructions() { + rewriteRun( + docker( + """ + FROM ubuntu:22.04 + LABEL author=dev + LABEL version=0.5.0 + RUN apt-get update + """, + """ + FROM ubuntu:22.04 + LABEL author=dev + LABEL version=1.0.0 + RUN apt-get update + """ + ) + ); + } + + @Test + void addToSpecificStage() { + rewriteRun( + spec -> spec.recipe(new AddOrUpdateLabel("version", "1.0.0", null, "final")), + docker( + """ + FROM golang:1.21 AS builder + RUN go build -o app . + + FROM alpine:3.18 AS final + COPY --from=builder /app /app + """, + """ + FROM golang:1.21 AS builder + RUN go build -o app . + + FROM alpine:3.18 AS final + LABEL version=1.0.0 + COPY --from=builder /app /app + """ + ) + ); + } + + @Test + void addOnlyToFinalStageByDefault() { + rewriteRun( + docker( + """ + FROM golang:1.21 AS builder + RUN go build -o app . + + FROM alpine:3.18 + COPY --from=builder /app /app + """, + """ + FROM golang:1.21 AS builder + RUN go build -o app . + + FROM alpine:3.18 + LABEL version=1.0.0 + COPY --from=builder /app /app + """ + ) + ); + } + + @Test + void insertAfterExistingLabels() { + rewriteRun( + spec -> spec.recipe(new AddOrUpdateLabel("newkey", "newvalue", null, null)), + docker( + """ + FROM ubuntu:22.04 + LABEL existing=value + RUN apt-get update + """, + """ + FROM ubuntu:22.04 + LABEL existing=value + LABEL newkey=newvalue + RUN apt-get update + """ + ) + ); + } + + @Test + void preserveOciLabelKey() { + rewriteRun( + spec -> spec.recipe(new AddOrUpdateLabel("org.opencontainers.image.version", "1.0.0", null, null)), + docker( + """ + FROM ubuntu:22.04 + RUN apt-get update + """, + """ + FROM ubuntu:22.04 + LABEL org.opencontainers.image.version=1.0.0 + RUN apt-get update + """ + ) + ); + } + + @Test + void noChangeWhenStageNotFound() { + rewriteRun( + spec -> spec.recipe(new AddOrUpdateLabel("version", "1.0.0", null, "nonexistent")), + docker( + """ + FROM ubuntu:22.04 + RUN apt-get update + """ + ) + ); + } +} diff --git a/rewrite-docker/src/test/java/org/openrewrite/docker/ChangeBaseImageTest.java b/rewrite-docker/src/test/java/org/openrewrite/docker/ChangeBaseImageTest.java new file mode 100644 index 0000000000..f5b4dcdf5a --- /dev/null +++ b/rewrite-docker/src/test/java/org/openrewrite/docker/ChangeBaseImageTest.java @@ -0,0 +1,360 @@ +/* + * Copyright 2026 the original author or authors. + *

+ * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * https://www.apache.org/licenses/LICENSE-2.0 + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.openrewrite.docker; + +import org.junit.jupiter.api.Test; +import org.openrewrite.DocumentExample; +import org.openrewrite.test.RecipeSpec; +import org.openrewrite.test.RewriteTest; + +import static org.openrewrite.docker.Assertions.docker; + +class ChangeBaseImageTest implements RewriteTest { + + @Override + public void defaults(RecipeSpec spec) { + spec.recipe(new ChangeBaseImage("ubuntu:20.04", "ubuntu:22.04", null, null)); + } + + @DocumentExample + @Test + void changeSimpleBaseImage() { + rewriteRun( + docker( + """ + FROM ubuntu:20.04 + RUN apt-get update + """, + """ + FROM ubuntu:22.04 + RUN apt-get update + """ + ) + ); + } + + @Test + void changeBaseImageWithFlags() { + rewriteRun( + docker( + """ + FROM --platform=linux/amd64 ubuntu:20.04 + RUN apt-get update + """, + """ + FROM --platform=linux/amd64 ubuntu:22.04 + RUN apt-get update + """ + ) + ); + } + + @Test + void changeBaseImageWithAs() { + rewriteRun( + spec -> spec.recipe(new ChangeBaseImage("golang:1.20", "golang:1.21", null, null)), + docker( + """ + FROM golang:1.20 AS builder + RUN go build -o app . + + FROM alpine:latest + COPY --from=builder /app /app + """, + """ + FROM golang:1.21 AS builder + RUN go build -o app . + + FROM alpine:latest + COPY --from=builder /app /app + """ + ) + ); + } + + @Test + void changeMultipleStages() { + rewriteRun( + docker( + """ + FROM ubuntu:20.04 AS base + RUN apt-get update + + FROM ubuntu:20.04 AS builder + RUN apt-get install -y build-essential + + FROM alpine:latest + COPY --from=builder /app /app + """, + """ + FROM ubuntu:22.04 AS base + RUN apt-get update + + FROM ubuntu:22.04 AS builder + RUN apt-get install -y build-essential + + FROM alpine:latest + COPY --from=builder /app /app + """ + ) + ); + } + + @Test + void dontChangeNonMatchingImage() { + rewriteRun( + docker( + """ + FROM alpine:latest + RUN apk add --no-cache ca-certificates + """ + ) + ); + } + + @Test + void changeWithGlobPattern() { + rewriteRun( + spec -> spec.recipe(new ChangeBaseImage("ubuntu:*", "ubuntu:22.04", null, null)), + docker( + """ + FROM ubuntu:20.04 + RUN apt-get update + """, + """ + FROM ubuntu:22.04 + RUN apt-get update + """ + ) + ); + } + + @Test + void changeWithGlobPatternMultipleMatches() { + rewriteRun( + spec -> spec.recipe(new ChangeBaseImage("ubuntu:*", "ubuntu:22.04", null, null)), + docker( + """ + FROM ubuntu:18.04 AS base + FROM ubuntu:20.04 AS builder + FROM alpine:latest + """, + """ + FROM ubuntu:22.04 AS base + FROM ubuntu:22.04 AS builder + FROM alpine:latest + """ + ) + ); + } + + @Test + void changeWithWildcardImageName() { + rewriteRun( + spec -> spec.recipe(new ChangeBaseImage("*/ubuntu:20.04", "docker.io/library/ubuntu:22.04", null, null)), + docker( + """ + FROM gcr.io/ubuntu:20.04 + """, + """ + FROM docker.io/library/ubuntu:22.04 + """ + ) + ); + } + + @Test + void addPlatformFlag() { + rewriteRun( + spec -> spec.recipe(new ChangeBaseImage("ubuntu:20.04", "ubuntu:20.04", null, "linux/arm64")), + docker( + """ + FROM ubuntu:20.04 + RUN apt-get update + """, + """ + FROM --platform=linux/arm64 ubuntu:20.04 + RUN apt-get update + """ + ) + ); + } + + @Test + void updatePlatformFlag() { + rewriteRun( + spec -> spec.recipe(new ChangeBaseImage("ubuntu:20.04", "ubuntu:20.04", null, "linux/arm64")), + docker( + """ + FROM --platform=linux/amd64 ubuntu:20.04 + RUN apt-get update + """, + """ + FROM --platform=linux/arm64 ubuntu:20.04 + RUN apt-get update + """ + ) + ); + } + + @Test + void removePlatformFlag() { + rewriteRun( + spec -> spec.recipe(new ChangeBaseImage("ubuntu:20.04", "ubuntu:20.04", "linux/amd64", null)), + docker( + """ + FROM --platform=linux/amd64 ubuntu:20.04 + RUN apt-get update + """, + """ + FROM ubuntu:20.04 + RUN apt-get update + """ + ) + ); + } + + @Test + void changeImageAndPlatform() { + rewriteRun( + spec -> spec.recipe(new ChangeBaseImage("ubuntu:20.04", "ubuntu:22.04", null, "linux/arm64")), + docker( + """ + FROM --platform=linux/amd64 ubuntu:20.04 + RUN apt-get update + """, + """ + FROM --platform=linux/arm64 ubuntu:22.04 + RUN apt-get update + """ + ) + ); + } + + @Test + void onlyChangeImageWithMatchingPlatform() { + rewriteRun( + spec -> spec.recipe(new ChangeBaseImage("ubuntu:20.04", "ubuntu:22.04", "linux/amd64", null)), + docker( + """ + FROM --platform=linux/amd64 ubuntu:20.04 + RUN apt-get update + + FROM --platform=linux/arm64 ubuntu:20.04 + RUN apt-get install -y nginx + """, + """ + FROM ubuntu:22.04 + RUN apt-get update + + FROM --platform=linux/arm64 ubuntu:20.04 + RUN apt-get install -y nginx + """ + ) + ); + } + + @Test + void changeImageAndPlatformWhenMatchingOldPlatform() { + rewriteRun( + spec -> spec.recipe(new ChangeBaseImage("ubuntu:20.04", "ubuntu:22.04", "linux/amd64", "linux/arm64")), + docker( + """ + FROM --platform=linux/amd64 ubuntu:20.04 + RUN apt-get update + + FROM ubuntu:20.04 + RUN apt-get install -y nginx + """, + """ + FROM --platform=linux/arm64 ubuntu:22.04 + RUN apt-get update + + FROM ubuntu:20.04 + RUN apt-get install -y nginx + """ + ) + ); + } + + @Test + void changeBaseImageWithDoubleQuotedString() { + rewriteRun( + spec -> spec.recipe(new ChangeBaseImage("ubuntu:20.04", "ubuntu:22.04", null, null)), + docker( + """ + FROM "ubuntu:20.04" + RUN apt-get update + """, + """ + FROM "ubuntu:22.04" + RUN apt-get update + """ + ) + ); + } + + @Test + void changeBaseImageWithDoubleQuotedStringPreservesAs() { + rewriteRun( + spec -> spec.recipe(new ChangeBaseImage("ubuntu:20.04", "ubuntu:22.04", null, null)), + docker( + """ + FROM "ubuntu:20.04" as builder + RUN apt-get update + """, + """ + FROM "ubuntu:22.04" as builder + RUN apt-get update + """ + ) + ); + } + + @Test + void changeBaseImageWithSingleQuotedString() { + rewriteRun( + spec -> spec.recipe(new ChangeBaseImage("ubuntu:20.04", "ubuntu:22.04", null, null)), + docker( + """ + FROM 'ubuntu:20.04' + RUN apt-get update + """, + """ + FROM 'ubuntu:22.04' + RUN apt-get update + """ + ) + ); + } + + @Test + void changeBaseImageWithSingleQuotedStringPreservesTrailingComment() { + rewriteRun( + spec -> spec.recipe(new ChangeBaseImage("ubuntu:20.04", "ubuntu:22.04", null, null)), + docker( + """ + FROM 'ubuntu:20.04' # Trailing comment + RUN apt-get update + """, + """ + FROM 'ubuntu:22.04' # Trailing comment + RUN apt-get update + """ + ) + ); + } +} diff --git a/rewrite-docker/src/test/java/org/openrewrite/docker/DockerParserTest.java b/rewrite-docker/src/test/java/org/openrewrite/docker/DockerParserTest.java new file mode 100644 index 0000000000..82f25a2070 --- /dev/null +++ b/rewrite-docker/src/test/java/org/openrewrite/docker/DockerParserTest.java @@ -0,0 +1,102 @@ +/* + * Copyright 2026 the original author or authors. + *

+ * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * https://www.apache.org/licenses/LICENSE-2.0 + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.openrewrite.docker; + +import org.junit.jupiter.api.Test; +import org.openrewrite.test.RewriteTest; + +import static org.openrewrite.docker.Assertions.docker; + +/** + * General integration tests for the Docker parser. + * Instruction-specific tests are located in the tree package. + */ +class DockerParserTest implements RewriteTest { + + @Test + void multipleInstructions() { + rewriteRun( + docker( + """ + FROM ubuntu:20.04 + RUN apt-get update + RUN apt-get install -y curl + """ + ) + ); + } + + @Test + void multiLineRunWithContinuation() { + rewriteRun( + docker( + """ + FROM alpine:latest + RUN apk --no-cache add \\ + \topenvpn + + RUN mkdir /usr/share/openvpn + RUN cp /etc/openvpn/* /usr/share/openvpn/ + + WORKDIR /etc/openvpn + ENTRYPOINT ["openvpn"] + """ + ) + ); + } + + @Test + void comprehensiveDockerfile() { + rewriteRun( + docker( + """ + # syntax=docker/dockerfile:1 + + # Build stage + FROM golang:1.20 AS builder + WORKDIR /build + COPY go.mod go.sum ./ + RUN go mod download + COPY . . + RUN CGO_ENABLED=0 go build -o app + + # Runtime stage + FROM alpine:latest + RUN apk add --no-cache ca-certificates + WORKDIR /app + COPY --from=builder /build/app . + EXPOSE 8080 + USER nobody + ENTRYPOINT ["./app"] + """ + ) + ); + } + + @Test + void multipleJsonArrayWhitespaceStyles() { + // Test different whitespace styles in JSON arrays are preserved + rewriteRun( + docker( + """ + FROM ubuntu:20.04 + CMD ["no-spaces"] + ENTRYPOINT [ "with-spaces" ] + """ + ) + ); + } +} diff --git a/rewrite-docker/src/test/java/org/openrewrite/docker/LocalDockerParser.java b/rewrite-docker/src/test/java/org/openrewrite/docker/LocalDockerParser.java new file mode 100644 index 0000000000..012e57be20 --- /dev/null +++ b/rewrite-docker/src/test/java/org/openrewrite/docker/LocalDockerParser.java @@ -0,0 +1,141 @@ +/* + * Copyright 2026 the original author or authors. + *

+ * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * https://www.apache.org/licenses/LICENSE-2.0 + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.openrewrite.docker; + +import org.openrewrite.InMemoryExecutionContext; +import org.openrewrite.ParseExceptionResult; +import org.openrewrite.Parser; +import org.openrewrite.SourceFile; +import org.openrewrite.tree.ParseError; + +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.PrintStream; +import java.io.UncheckedIOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Stream; + +import static java.util.stream.Collectors.toList; + +public class LocalDockerParser { + + public static void main(String... args) throws Exception { + if (args.length < 2) { + System.err.println("Usage: LocalDockerParser "); + System.exit(1); + } + + Path dir = Path.of(args[0]); + Path outputFile = Path.of(args[1]); + if (!Files.isDirectory(dir)) { + System.err.println("Error: " + dir + " is not a directory"); + System.exit(1); + } + + ParseResult result = parse(dir); + printSummary(outputFile, result.parsedFiles(), result.parsedErrors()); + System.out.println("Output written to: " + outputFile); + } + + static ParseResult parse(Path dir) { + DockerParser parser = DockerParser.builder().build(); + + List dockerFiles = findDockerFiles(dir, parser); + if (dockerFiles.isEmpty()) { + System.err.println("No Dockerfiles or Containerfiles found in " + dir); + return new ParseResult(List.of(), Map.of()); + } + + List inputs = dockerFiles.stream() + .map(Parser.Input::fromFile) + .collect(toList()); + + List parsedFiles = new ArrayList<>(); + Map parsedErrors = new LinkedHashMap<>(); + List throwables = new ArrayList<>(); + InMemoryExecutionContext ctx = new InMemoryExecutionContext(throwables::add); + parser.parseInputs(inputs, dir, ctx).forEach(sourceFile -> { + if (sourceFile instanceof ParseError parseError) { + String errorMsg = parseError.getMarkers() + .findFirst(ParseExceptionResult.class) + .map(ex -> { + // Extract just the first line of the message (the actual error) + String msg = ex.getMessage(); + int newlineIdx = msg.indexOf('\n'); + return newlineIdx > 0 ? msg.substring(0, newlineIdx) : msg; + }) + .orElse("Unknown error"); + parsedErrors.put(parseError.getSourcePath(), errorMsg); + } else { + parsedFiles.add(sourceFile); + } + }); + for (Throwable error : throwables) { + if (error instanceof DockerParsingException dockerEx) { + parsedErrors.put(dockerEx.getPath(), dockerEx.getMessage()); + } + } + return new ParseResult(parsedFiles, parsedErrors); + } + + record ParseResult( + List parsedFiles, + Map parsedErrors) { + } + + private static List findDockerFiles(Path dir, DockerParser parser) { + try (Stream paths = Files.walk(dir)) { + return paths + .filter(Files::isRegularFile) + .filter(parser::accept) + .sorted() + .collect(toList()); + } catch (IOException e) { + throw new UncheckedIOException("Failed to walk directory: " + dir, e); + } + } + + private static void printSummary(Path outputFile, List parsedFiles, Map parsedErrors) throws Exception { + try (PrintStream out = new PrintStream(new FileOutputStream(outputFile.toFile()))) { + out.println("Files parsed successfully: " + parsedFiles.size()); + out.println("Files failed to parse: " + parsedErrors.size()); + out.println(); + + if (!parsedErrors.isEmpty()) { + // Group failures by error message + Map> errorGroups = new LinkedHashMap<>(); + for (Map.Entry entry : parsedErrors.entrySet()) { + errorGroups.computeIfAbsent(entry.getValue(), k -> new ArrayList<>()) + .add(entry.getKey()); + } + + for (Map.Entry> group : errorGroups.entrySet()) { + out.println(" ERROR: " + group.getKey()); + out.println(" Files affected (" + group.getValue().size() + "):"); + for (Path path : group.getValue()) { + out.println(" - " + path); + } + out.println(); + } + } + } + } +} diff --git a/rewrite-docker/src/test/java/org/openrewrite/docker/LocalDockerParserTest.java b/rewrite-docker/src/test/java/org/openrewrite/docker/LocalDockerParserTest.java new file mode 100644 index 0000000000..c53e440c87 --- /dev/null +++ b/rewrite-docker/src/test/java/org/openrewrite/docker/LocalDockerParserTest.java @@ -0,0 +1,60 @@ +/* + * Copyright 2026 the original author or authors. + *

+ * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * https://www.apache.org/licenses/LICENSE-2.0 + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.openrewrite.docker; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.condition.EnabledIf; +import org.openrewrite.SourceFile; + +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.List; +import java.util.Map; + +import static java.util.stream.Collectors.groupingBy; +import static org.assertj.core.api.Assertions.assertThat; + +class LocalDockerParserTest { + + static final Path DOCKER_FILES = Path.of("/home/tim/Documents/workspace/docker-images/"); + + static boolean dockerImagesDirExists() { + return Files.isDirectory(DOCKER_FILES); + } + + @Test + @EnabledIf("dockerImagesDirExists") + void parseDockerImagesDirectory() { + var parseResult = LocalDockerParser.parse(DOCKER_FILES); + + List parsedFiles = parseResult.parsedFiles(); + Map parsedErrors = parseResult.parsedErrors(); + System.out.println("Files parsed successfully: " + parsedFiles.size()); + System.out.println("Files failed to parse: " + parsedErrors.size()); + + parsedErrors.entrySet().stream() + .collect(groupingBy(e -> e.getValue() + .replace(e.getKey().toString(), "{}") + .replaceAll("at line \\d+:\\d+", "at line {}:{}"))) + .forEach((errorMsg, entries) -> { + System.out.println(entries.size() + "x : " + errorMsg); + entries.forEach(e -> System.out.println(" " + e.getValue().replace(DOCKER_FILES.toString(), ""))); + }); + + assertThat(parsedFiles).hasSizeGreaterThanOrEqualTo(1491); + assertThat(parsedErrors).hasSizeLessThanOrEqualTo(5); + } +} diff --git a/rewrite-docker/src/test/java/org/openrewrite/docker/search/FindDockerBaseImagesTest.java b/rewrite-docker/src/test/java/org/openrewrite/docker/search/FindDockerBaseImagesTest.java new file mode 100644 index 0000000000..ae544e6f8d --- /dev/null +++ b/rewrite-docker/src/test/java/org/openrewrite/docker/search/FindDockerBaseImagesTest.java @@ -0,0 +1,294 @@ +/* + * Copyright 2026 the original author or authors. + *

+ * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * https://www.apache.org/licenses/LICENSE-2.0 + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.openrewrite.docker.search; + +import org.junit.jupiter.api.Test; +import org.openrewrite.DocumentExample; +import org.openrewrite.docker.Assertions; +import org.openrewrite.docker.table.DockerBaseImages; +import org.openrewrite.test.RecipeSpec; +import org.openrewrite.test.RewriteTest; + +class FindDockerBaseImagesTest implements RewriteTest { + + @Override + public void defaults(RecipeSpec spec) { + spec.recipe(new FindDockerBaseImages(null)); + } + + @DocumentExample + @Test + void findAllBaseImages() { + rewriteRun( + spec -> spec.dataTableAsCsv(DockerBaseImages.class.getName(), + //language=csv + """ + sourceFile,stageName,imageName,tag,digest,platform + Dockerfile,,ubuntu,22.04,, + """ + ), + Assertions.docker( + """ + FROM ubuntu:22.04 + RUN apt-get update + """, + """ + ~~(ubuntu:22.04)~~>FROM ubuntu:22.04 + RUN apt-get update + """ + ) + ); + } + + @Test + void findBaseImageWithPattern() { + rewriteRun( + spec -> spec.recipe(new FindDockerBaseImages("ubuntu*")) + .dataTableAsCsv(DockerBaseImages.class.getName(), + //language=csv + """ + sourceFile,stageName,imageName,tag,digest,platform + Dockerfile,,ubuntu,22.04,, + """ + ), + Assertions.docker( + """ + FROM ubuntu:22.04 + RUN apt-get update + """, + """ + ~~(ubuntu:22.04)~~>FROM ubuntu:22.04 + RUN apt-get update + """ + ) + ); + } + + @Test + void noMatchWithPattern() { + rewriteRun( + spec -> spec.recipe(new FindDockerBaseImages("alpine*")), + Assertions.docker( + """ + FROM ubuntu:22.04 + RUN apt-get update + """ + ) + ); + } + + @Test + void findBaseImageWithDigest() { + rewriteRun( + spec -> spec.dataTableAsCsv(DockerBaseImages.class.getName(), + //language=csv + """ + sourceFile,stageName,imageName,tag,digest,platform + Dockerfile,,ubuntu,,sha256:abc123, + """ + ), + Assertions.docker( + """ + FROM ubuntu@sha256:abc123 + RUN apt-get update + """, + """ + ~~(ubuntu@sha256:abc123)~~>FROM ubuntu@sha256:abc123 + RUN apt-get update + """ + ) + ); + } + + @Test + void findBaseImageWithPlatform() { + rewriteRun( + spec -> spec.dataTableAsCsv(DockerBaseImages.class.getName(), + //language=csv + """ + sourceFile,stageName,imageName,tag,digest,platform + Dockerfile,,ubuntu,22.04,,linux/amd64 + """ + ), + Assertions.docker( + """ + FROM --platform=linux/amd64 ubuntu:22.04 + RUN apt-get update + """, + """ + ~~(ubuntu:22.04)~~>FROM --platform=linux/amd64 ubuntu:22.04 + RUN apt-get update + """ + ) + ); + } + + @Test + void findBaseImageWithStageName() { + rewriteRun( + spec -> spec.dataTableAsCsv(DockerBaseImages.class.getName(), + //language=csv + """ + sourceFile,stageName,imageName,tag,digest,platform + Dockerfile,builder,golang,1.21,, + """ + ), + Assertions.docker( + """ + FROM golang:1.21 AS builder + RUN go build -o app . + """, + """ + ~~(golang:1.21)~~>FROM golang:1.21 AS builder + RUN go build -o app . + """ + ) + ); + } + + @Test + void findMultipleBaseImages() { + rewriteRun( + spec -> spec.dataTableAsCsv(DockerBaseImages.class.getName(), + //language=csv + """ + sourceFile,stageName,imageName,tag,digest,platform + Dockerfile,builder,golang,1.21,, + Dockerfile,,alpine,latest,, + """ + ), + Assertions.docker( + """ + FROM golang:1.21 AS builder + RUN go build -o app . + + FROM alpine:latest + COPY --from=builder /app /app + """, + """ + ~~(golang:1.21)~~>FROM golang:1.21 AS builder + RUN go build -o app . + + ~~(alpine:latest)~~>FROM alpine:latest + COPY --from=builder /app /app + """ + ) + ); + } + + @Test + void filterByPatternInMultiStage() { + rewriteRun( + spec -> spec.recipe(new FindDockerBaseImages("alpine*")) + .dataTableAsCsv(DockerBaseImages.class.getName(), + //language=csv + """ + sourceFile,stageName,imageName,tag,digest,platform + Dockerfile,,alpine,latest,, + """ + ), + Assertions.docker( + """ + FROM golang:1.21 AS builder + RUN go build -o app . + + FROM alpine:latest + COPY --from=builder /app /app + """, + """ + FROM golang:1.21 AS builder + RUN go build -o app . + + ~~(alpine:latest)~~>FROM alpine:latest + COPY --from=builder /app /app + """ + ) + ); + } + + @Test + void findBaseImageWithAllDetails() { + rewriteRun( + spec -> spec.dataTableAsCsv(DockerBaseImages.class.getName(), + //language=csv + """ + sourceFile,stageName,imageName,tag,digest,platform + Dockerfile,base,ubuntu,22.04,,linux/arm64 + """ + ), + Assertions.docker( + """ + FROM --platform=linux/arm64 ubuntu:22.04 AS base + RUN apt-get update + """, + """ + ~~(ubuntu:22.04)~~>FROM --platform=linux/arm64 ubuntu:22.04 AS base + RUN apt-get update + """ + ) + ); + } + + @Test + void findBaseImageWithoutTag() { + rewriteRun( + spec -> spec.dataTableAsCsv(DockerBaseImages.class.getName(), + //language=csv + """ + sourceFile,stageName,imageName,tag,digest,platform + Dockerfile,,ubuntu,,, + """ + ), + Assertions.docker( + """ + FROM ubuntu + RUN apt-get update + """, + """ + ~~(ubuntu)~~>FROM ubuntu + RUN apt-get update + """ + ) + ); + } + + @Test + void findBaseImageWithGlobalArg() { + rewriteRun( + spec -> spec.dataTableAsCsv(DockerBaseImages.class.getName(), + //language=csv + """ + sourceFile,stageName,imageName,tag,digest,platform + Dockerfile,,"${BASE_IMAGE}","${BASE_TAG}",, + """ + ), + Assertions.docker( + """ + ARG BASE_IMAGE=ubuntu + ARG BASE_TAG=22.04 + FROM ${BASE_IMAGE}:${BASE_TAG} + RUN apt-get update + """, + """ + ARG BASE_IMAGE=ubuntu + ARG BASE_TAG=22.04 + ~~(${BASE_IMAGE}:${BASE_TAG})~~>FROM ${BASE_IMAGE}:${BASE_TAG} + RUN apt-get update + """ + ) + ); + } +} diff --git a/rewrite-docker/src/test/java/org/openrewrite/docker/search/FindExposedPortsTest.java b/rewrite-docker/src/test/java/org/openrewrite/docker/search/FindExposedPortsTest.java new file mode 100644 index 0000000000..d98e8e79dc --- /dev/null +++ b/rewrite-docker/src/test/java/org/openrewrite/docker/search/FindExposedPortsTest.java @@ -0,0 +1,239 @@ +/* + * Copyright 2026 the original author or authors. + *

+ * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * https://www.apache.org/licenses/LICENSE-2.0 + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.openrewrite.docker.search; + +import org.junit.jupiter.api.Test; +import org.openrewrite.DocumentExample; +import org.openrewrite.docker.table.DockerExposedPorts; +import org.openrewrite.test.RecipeSpec; +import org.openrewrite.test.RewriteTest; + +import static org.openrewrite.docker.Assertions.docker; + +class FindExposedPortsTest implements RewriteTest { + + @Override + public void defaults(RecipeSpec spec) { + spec.recipe(new FindExposedPorts(null)); + } + + @DocumentExample + @Test + void findSinglePort() { + rewriteRun( + spec -> spec.dataTableAsCsv(DockerExposedPorts.class.getName(), + """ + sourceFile,stageName,port,protocol + Dockerfile,,80, + """ + ), + docker( + """ + FROM nginx:latest + EXPOSE 80 + """, + """ + FROM nginx:latest + ~~(80)~~>EXPOSE 80 + """ + ) + ); + } + + @Test + void findMultiplePorts() { + rewriteRun( + spec -> spec.dataTableAsCsv(DockerExposedPorts.class.getName(), + """ + sourceFile,stageName,port,protocol + Dockerfile,,80, + Dockerfile,,443, + """ + ), + docker( + """ + FROM nginx:latest + EXPOSE 80 443 + """, + """ + FROM nginx:latest + ~~(80, 443)~~>EXPOSE 80 443 + """ + ) + ); + } + + @Test + void findPortWithProtocol() { + rewriteRun( + spec -> spec.dataTableAsCsv(DockerExposedPorts.class.getName(), + """ + sourceFile,stageName,port,protocol + Dockerfile,,53,udp + Dockerfile,,53,tcp + """ + ), + docker( + """ + FROM ubuntu:22.04 + EXPOSE 53/udp 53/tcp + """, + """ + FROM ubuntu:22.04 + ~~(53/udp, 53/tcp)~~>EXPOSE 53/udp 53/tcp + """ + ) + ); + } + + @Test + void findPortRange() { + rewriteRun( + spec -> spec.dataTableAsCsv(DockerExposedPorts.class.getName(), + """ + sourceFile,stageName,port,protocol + Dockerfile,,8000-8100, + """ + ), + docker( + """ + FROM ubuntu:22.04 + EXPOSE 8000-8100 + """, + """ + FROM ubuntu:22.04 + ~~(8000-8100)~~>EXPOSE 8000-8100 + """ + ) + ); + } + + @Test + void findPortWithStageName() { + rewriteRun( + spec -> spec.dataTableAsCsv(DockerExposedPorts.class.getName(), + """ + sourceFile,stageName,port,protocol + Dockerfile,app,8080, + """ + ), + docker( + """ + FROM golang:1.21 AS builder + RUN go build -o app . + + FROM alpine:3.18 AS app + EXPOSE 8080 + COPY --from=builder /app /app + """, + """ + FROM golang:1.21 AS builder + RUN go build -o app . + + FROM alpine:3.18 AS app + ~~(8080)~~>EXPOSE 8080 + COPY --from=builder /app /app + """ + ) + ); + } + + @Test + void filterByPortPattern() { + rewriteRun( + spec -> spec.recipe(new FindExposedPorts("80*")) + .dataTableAsCsv(DockerExposedPorts.class.getName(), + """ + sourceFile,stageName,port,protocol + Dockerfile,,80, + Dockerfile,,8080, + """ + ), + docker( + """ + FROM nginx:latest + EXPOSE 80 443 8080 + """, + """ + FROM nginx:latest + ~~(80, 8080)~~>EXPOSE 80 443 8080 + """ + ) + ); + } + + @Test + void filterByProtocol() { + rewriteRun( + spec -> spec.recipe(new FindExposedPorts("*/udp")) + .dataTableAsCsv(DockerExposedPorts.class.getName(), + """ + sourceFile,stageName,port,protocol + Dockerfile,,53,udp + """ + ), + docker( + """ + FROM ubuntu:22.04 + EXPOSE 53/udp 53/tcp + """, + """ + FROM ubuntu:22.04 + ~~(53/udp)~~>EXPOSE 53/udp 53/tcp + """ + ) + ); + } + + @Test + void noMatchWithPattern() { + rewriteRun( + spec -> spec.recipe(new FindExposedPorts("9999")), + docker( + """ + FROM nginx:latest + EXPOSE 80 443 + """ + ) + ); + } + + @Test + void multipleExposeInstructions() { + rewriteRun( + spec -> spec.dataTableAsCsv(DockerExposedPorts.class.getName(), + """ + sourceFile,stageName,port,protocol + Dockerfile,,80, + Dockerfile,,443, + """ + ), + docker( + """ + FROM nginx:latest + EXPOSE 80 + EXPOSE 443 + """, + """ + FROM nginx:latest + ~~(80)~~>EXPOSE 80 + ~~(443)~~>EXPOSE 443 + """ + ) + ); + } + +} diff --git a/rewrite-docker/src/test/java/org/openrewrite/docker/search/FindMissingHealthcheckTest.java b/rewrite-docker/src/test/java/org/openrewrite/docker/search/FindMissingHealthcheckTest.java new file mode 100644 index 0000000000..4deb4e2d74 --- /dev/null +++ b/rewrite-docker/src/test/java/org/openrewrite/docker/search/FindMissingHealthcheckTest.java @@ -0,0 +1,128 @@ +/* + * Copyright 2026 the original author or authors. + *

+ * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * https://www.apache.org/licenses/LICENSE-2.0 + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.openrewrite.docker.search; + +import org.junit.jupiter.api.Test; +import org.openrewrite.DocumentExample; +import org.openrewrite.test.RecipeSpec; +import org.openrewrite.test.RewriteTest; + +import static org.openrewrite.docker.Assertions.docker; + +class FindMissingHealthcheckTest implements RewriteTest { + + @Override + public void defaults(RecipeSpec spec) { + spec.recipe(new FindMissingHealthcheck()); + } + + @DocumentExample + @Test + void detectMissingHealthcheck() { + rewriteRun( + docker( + """ + FROM alpine:3.18 + CMD ["./app"] + """, + """ + ~~(Missing HEALTHCHECK instruction)~~>FROM alpine:3.18 + CMD ["./app"] + """ + ) + ); + } + + @Test + void healthcheckPresent() { + rewriteRun( + docker( + """ + FROM alpine:3.18 + HEALTHCHECK CMD curl -f http://localhost/ || exit 1 + CMD ["./app"] + """ + ) + ); + } + + @Test + void healthcheckNonePresent() { + rewriteRun( + docker( + """ + FROM alpine:3.18 + HEALTHCHECK NONE + CMD ["./app"] + """ + ) + ); + } + + @Test + void multiStageOnlyChecksFinalStage() { + rewriteRun( + docker( + """ + FROM golang:1.21 AS builder + RUN go build -o app . + + FROM alpine:3.18 + HEALTHCHECK CMD curl -f http://localhost/ || exit 1 + COPY --from=builder /app /app + """ + ) + ); + } + + @Test + void multiStageDetectsMissingHealthcheckInFinalStage() { + rewriteRun( + docker( + """ + FROM golang:1.21 AS builder + HEALTHCHECK CMD go test + RUN go build -o app . + + FROM alpine:3.18 + COPY --from=builder /app /app + """, + """ + FROM golang:1.21 AS builder + HEALTHCHECK CMD go test + RUN go build -o app . + + ~~(Missing HEALTHCHECK instruction)~~>FROM alpine:3.18 + COPY --from=builder /app /app + """ + ) + ); + } + + @Test + void singleStageWithHealthcheck() { + rewriteRun( + docker( + """ + FROM nginx:1.25 + COPY nginx.conf /etc/nginx/nginx.conf + HEALTHCHECK --interval=30s --timeout=3s CMD curl -f http://localhost/ || exit 1 + EXPOSE 80 + """ + ) + ); + } +} diff --git a/rewrite-docker/src/test/java/org/openrewrite/docker/search/FindRootUserTest.java b/rewrite-docker/src/test/java/org/openrewrite/docker/search/FindRootUserTest.java new file mode 100644 index 0000000000..1f6277a600 --- /dev/null +++ b/rewrite-docker/src/test/java/org/openrewrite/docker/search/FindRootUserTest.java @@ -0,0 +1,169 @@ +/* + * Copyright 2026 the original author or authors. + *

+ * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * https://www.apache.org/licenses/LICENSE-2.0 + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.openrewrite.docker.search; + +import org.junit.jupiter.api.Test; +import org.openrewrite.DocumentExample; +import org.openrewrite.test.RecipeSpec; +import org.openrewrite.test.RewriteTest; + +import static org.openrewrite.docker.Assertions.docker; + +class FindRootUserTest implements RewriteTest { + + @Override + public void defaults(RecipeSpec spec) { + spec.recipe(new FindRootUser(null)); + } + + @DocumentExample + @Test + void detectMissingUser() { + rewriteRun( + docker( + """ + FROM ubuntu:22.04 + RUN apt-get update + """, + """ + ~~(No USER instruction, runs as root)~~>FROM ubuntu:22.04 + RUN apt-get update + """ + ) + ); + } + + @Test + void detectExplicitRoot() { + rewriteRun( + docker( + """ + FROM ubuntu:22.04 + USER root + RUN apt-get update + """, + """ + FROM ubuntu:22.04 + ~~(Explicitly runs as root)~~>USER root + RUN apt-get update + """ + ) + ); + } + + @Test + void detectUserZero() { + rewriteRun( + docker( + """ + FROM ubuntu:22.04 + USER 0 + RUN apt-get update + """, + """ + FROM ubuntu:22.04 + ~~(Explicitly runs as root)~~>USER 0 + RUN apt-get update + """ + ) + ); + } + + @Test + void nonRootUserIsOk() { + rewriteRun( + docker( + """ + FROM ubuntu:22.04 + USER appuser + RUN apt-get update + """ + ) + ); + } + + @Test + void multiStageOnlyChecksFinalStage() { + rewriteRun( + docker( + """ + FROM golang:1.21 AS builder + RUN go build -o app . + + FROM alpine:3.18 + USER appuser + COPY --from=builder /app /app + """ + ) + ); + } + + @Test + void multiStageDetectsMissingUserInFinalStage() { + rewriteRun( + docker( + """ + FROM golang:1.21 AS builder + USER builder + RUN go build -o app . + + FROM alpine:3.18 + COPY --from=builder /app /app + """, + """ + FROM golang:1.21 AS builder + USER builder + RUN go build -o app . + + ~~(No USER instruction, runs as root)~~>FROM alpine:3.18 + COPY --from=builder /app /app + """ + ) + ); + } + + @Test + void disableMissingUserCheck() { + rewriteRun( + spec -> spec.recipe(new FindRootUser(false)), + docker( + """ + FROM ubuntu:22.04 + RUN apt-get update + """ + ) + ); + } + + @Test + void disableMissingUserCheckStillFindsExplicitRoot() { + rewriteRun( + spec -> spec.recipe(new FindRootUser(false)), + docker( + """ + FROM ubuntu:22.04 + USER root + RUN apt-get update + """, + """ + FROM ubuntu:22.04 + ~~(Explicitly runs as root)~~>USER root + RUN apt-get update + """ + ) + ); + } +} diff --git a/rewrite-docker/src/test/java/org/openrewrite/docker/search/FindUnpinnedBaseImagesTest.java b/rewrite-docker/src/test/java/org/openrewrite/docker/search/FindUnpinnedBaseImagesTest.java new file mode 100644 index 0000000000..d5dfac52aa --- /dev/null +++ b/rewrite-docker/src/test/java/org/openrewrite/docker/search/FindUnpinnedBaseImagesTest.java @@ -0,0 +1,145 @@ +/* + * Copyright 2026 the original author or authors. + *

+ * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * https://www.apache.org/licenses/LICENSE-2.0 + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.openrewrite.docker.search; + +import org.junit.jupiter.api.Test; +import org.openrewrite.DocumentExample; +import org.openrewrite.test.RecipeSpec; +import org.openrewrite.test.RewriteTest; + +import static org.openrewrite.docker.Assertions.docker; + +class FindUnpinnedBaseImagesTest implements RewriteTest { + + @Override + public void defaults(RecipeSpec spec) { + spec.recipe(new FindUnpinnedBaseImages()); + } + + @DocumentExample + @Test + void detectLatestTag() { + rewriteRun( + docker( + """ + FROM ubuntu:latest + RUN apt-get update + """, + """ + ~~(Uses 'latest' tag)~~>FROM ubuntu:latest + RUN apt-get update + """ + ) + ); + } + + @Test + void detectNoTag() { + rewriteRun( + docker( + """ + FROM ubuntu + RUN apt-get update + """, + """ + ~~(Uses implicit 'latest' tag)~~>FROM ubuntu + RUN apt-get update + """ + ) + ); + } + + @Test + void pinnedDigestIsOk() { + rewriteRun( + docker( + """ + FROM ubuntu@sha256:abc123def456 + RUN apt-get update + """ + ) + ); + } + + @Test + void pinnedTagIsOk() { + rewriteRun( + docker( + """ + FROM ubuntu:22.04 + RUN apt-get update + """ + ) + ); + } + + @Test + void scratchImageIsOk() { + rewriteRun( + docker( + """ + FROM scratch + COPY app /app + ENTRYPOINT ["/app"] + """ + ) + ); + } + + @Test + void multiStageMarksAllUnpinned() { + rewriteRun( + docker( + """ + FROM golang AS builder + RUN go build -o app . + + FROM alpine:latest + COPY --from=builder /app /app + """, + """ + ~~(Uses implicit 'latest' tag)~~>FROM golang AS builder + RUN go build -o app . + + ~~(Uses 'latest' tag)~~>FROM alpine:latest + COPY --from=builder /app /app + """ + ) + ); + } + + @Test + void multiStageWithMixedPinning() { + rewriteRun( + docker( + """ + FROM golang:1.21 AS builder + RUN go build -o app . + + FROM alpine + COPY --from=builder /app /app + """, + """ + FROM golang:1.21 AS builder + RUN go build -o app . + + ~~(Uses implicit 'latest' tag)~~>FROM alpine + COPY --from=builder /app /app + """ + ) + ); + } +} diff --git a/rewrite-docker/src/test/java/org/openrewrite/docker/tree/AddTest.java b/rewrite-docker/src/test/java/org/openrewrite/docker/tree/AddTest.java new file mode 100644 index 0000000000..8ecf8c9bb3 --- /dev/null +++ b/rewrite-docker/src/test/java/org/openrewrite/docker/tree/AddTest.java @@ -0,0 +1,227 @@ +/* + * Copyright 2026 the original author or authors. + *

+ * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * https://www.apache.org/licenses/LICENSE-2.0 + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.openrewrite.docker.tree; + +import org.junit.jupiter.api.Test; +import org.openrewrite.test.RewriteTest; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.openrewrite.docker.Assertions.docker; + +class AddTest implements RewriteTest { + + @Test + void addInstruction() { + rewriteRun( + docker( + """ + FROM ubuntu:20.04 + ADD app.tar.gz /app/ + """, + spec -> spec.afterRecipe(doc -> { + Docker.Add add = (Docker.Add) doc.getStages().getFirst().getInstructions().getLast(); + assertThat(add.getShellForm()).isNotNull(); + assertThat(add.getShellForm().getSources()).hasSize(1); + assertThat(((Docker.Literal) add.getShellForm().getSources().getFirst().getContents().getFirst()).getText()).isEqualTo("app.tar.gz"); + assertThat(((Docker.Literal) add.getShellForm().getDestination().getContents().getFirst()).getText()).isEqualTo("/app/"); + }) + ) + ); + } + + @Test + void addWithMultipleSources() { + // ADD with multiple source files + rewriteRun( + docker( + """ + FROM ubuntu:20.04 + ADD file1.txt file2.txt file3.tar.gz /data/ + """, + spec -> spec.afterRecipe(doc -> { + Docker.Add add = (Docker.Add) doc.getStages().getFirst().getInstructions().getLast(); + assertThat(add.getShellForm()).isNotNull(); + assertThat(add.getExecForm()).isNull(); + assertThat(add.getHeredoc()).isNull(); + + // Verify sources + assertThat(add.getShellForm().getSources()).hasSize(3); + assertThat(((Docker.Literal) add.getShellForm().getSources().getFirst().getContents().getFirst()).getText()) + .isEqualTo("file1.txt"); + assertThat(((Docker.Literal) add.getShellForm().getSources().get(1).getContents().getFirst()).getText()) + .isEqualTo("file2.txt"); + assertThat(((Docker.Literal) add.getShellForm().getSources().getLast().getContents().getFirst()).getText()) + .isEqualTo("file3.tar.gz"); + + // Verify destination + assertThat(((Docker.Literal) add.getShellForm().getDestination().getContents().getFirst()).getText()) + .isEqualTo("/data/"); + }) + ) + ); + } + + @Test + void addWithWildcards() { + // ADD with wildcard patterns + rewriteRun( + docker( + """ + FROM ubuntu:20.04 + ADD *.txt /docs/ + ADD config?.yaml /etc/app/ + """, + spec -> spec.afterRecipe(doc -> { + var instructions = doc.getStages().getFirst().getInstructions(); + + // First ADD with * wildcard + Docker.Add add1 = (Docker.Add) instructions.getFirst(); + assertThat(add1.getShellForm()).isNotNull(); + assertThat(add1.getShellForm().getSources()).hasSize(1); + assertThat(((Docker.Literal) add1.getShellForm().getSources().getFirst().getContents().getFirst()).getText()) + .isEqualTo("*.txt"); + assertThat(((Docker.Literal) add1.getShellForm().getDestination().getContents().getFirst()).getText()) + .isEqualTo("/docs/"); + + // Second ADD with ? wildcard + Docker.Add add2 = (Docker.Add) instructions.getLast(); + assertThat(add2.getShellForm()).isNotNull(); + assertThat(add2.getShellForm().getSources()).hasSize(1); + assertThat(((Docker.Literal) add2.getShellForm().getSources().getFirst().getContents().getFirst()).getText()) + .isEqualTo("config?.yaml"); + assertThat(((Docker.Literal) add2.getShellForm().getDestination().getContents().getFirst()).getText()) + .isEqualTo("/etc/app/"); + }) + ) + ); + } + + @Test + void addWithFlags() { + // ADD with --chown and --chmod flags + rewriteRun( + docker( + """ + FROM ubuntu:20.04 + ADD --chown=user:group archive.tar.gz /app/ + ADD --chmod=755 script.sh /usr/local/bin/ + ADD --chown=root --chmod=644 config.conf /etc/ + """, + spec -> spec.afterRecipe(doc -> { + var instructions = doc.getStages().getFirst().getInstructions(); + + // First ADD with --chown + Docker.Add add1 = (Docker.Add) instructions.getFirst(); + assertThat(add1.getFlags()).hasSize(1); + assertThat(add1.getFlags().getFirst().getName()).isEqualTo("chown"); + assertThat(((Docker.Literal) add1.getFlags().getFirst().getValue().getContents().getFirst()).getText()) + .isEqualTo("user:group"); + assertThat(add1.getShellForm()).isNotNull(); + assertThat(((Docker.Literal) add1.getShellForm().getSources().getFirst().getContents().getFirst()).getText()) + .isEqualTo("archive.tar.gz"); + + // Second ADD with --chmod + Docker.Add add2 = (Docker.Add) instructions.get(1); + assertThat(add2.getFlags()).hasSize(1); + assertThat(add2.getFlags().getFirst().getName()).isEqualTo("chmod"); + assertThat(((Docker.Literal) add2.getFlags().getFirst().getValue().getContents().getFirst()).getText()) + .isEqualTo("755"); + assertThat(add2.getShellForm()).isNotNull(); + assertThat(((Docker.Literal) add2.getShellForm().getSources().getFirst().getContents().getFirst()).getText()) + .isEqualTo("script.sh"); + + // Third ADD with both --chown and --chmod + Docker.Add add3 = (Docker.Add) instructions.getLast(); + assertThat(add3.getFlags()).hasSize(2); + assertThat(add3.getFlags().getFirst().getName()).isEqualTo("chown"); + assertThat(((Docker.Literal) add3.getFlags().getFirst().getValue().getContents().getFirst()).getText()) + .isEqualTo("root"); + assertThat(add3.getFlags().getLast().getName()).isEqualTo("chmod"); + assertThat(((Docker.Literal) add3.getFlags().getLast().getValue().getContents().getFirst()).getText()) + .isEqualTo("644"); + }) + ) + ); + } + + @Test + void addWithArrayNotation() { + // ADD with exec form (JSON array notation) + rewriteRun( + docker( + """ + FROM ubuntu:20.04 + ADD ["source file.txt", "another file.txt", "/dest dir/"] + """, + spec -> spec.afterRecipe(doc -> { + Docker.Add add = (Docker.Add) doc.getStages().getFirst().getInstructions().getLast(); + + // Verify exec form is used + assertThat(add.getExecForm()).isNotNull(); + assertThat(add.getShellForm()).isNull(); + assertThat(add.getHeredoc()).isNull(); + + // Verify arguments + var args = add.getExecForm().getArguments(); + assertThat(args).hasSize(3); + + // First source (with space in name) + assertThat(args.getFirst().getText()).isEqualTo("source file.txt"); + assertThat(args.getFirst().getQuoteStyle()).isEqualTo(Docker.Literal.QuoteStyle.DOUBLE); + + // Second source (with space in name) + assertThat(args.get(1).getText()).isEqualTo("another file.txt"); + assertThat(args.get(1).getQuoteStyle()).isEqualTo(Docker.Literal.QuoteStyle.DOUBLE); + + // Destination (with space in path) + assertThat(args.getLast().getText()).isEqualTo("/dest dir/"); + assertThat(args.getLast().getQuoteStyle()).isEqualTo(Docker.Literal.QuoteStyle.DOUBLE); + }) + ) + ); + } + + @Test + void addWithArrayNotationAndFlags() { + // ADD with flags and exec form + rewriteRun( + docker( + """ + FROM ubuntu:20.04 + ADD --chown=app:app ["config.json", "/app/config/"] + """, + spec -> spec.afterRecipe(doc -> { + Docker.Add add = (Docker.Add) doc.getStages().getFirst().getInstructions().getLast(); + + // Verify flags + assertThat(add.getFlags()).hasSize(1); + assertThat(add.getFlags().getFirst().getName()).isEqualTo("chown"); + assertThat(((Docker.Literal) add.getFlags().getFirst().getValue().getContents().getFirst()).getText()) + .isEqualTo("app:app"); + + // Verify exec form + assertThat(add.getExecForm()).isNotNull(); + assertThat(add.getShellForm()).isNull(); + + var args = add.getExecForm().getArguments(); + assertThat(args).hasSize(2); + assertThat(args.getFirst().getText()).isEqualTo("config.json"); + assertThat(args.getLast().getText()).isEqualTo("/app/config/"); + }) + ) + ); + } +} diff --git a/rewrite-docker/src/test/java/org/openrewrite/docker/tree/ArgTest.java b/rewrite-docker/src/test/java/org/openrewrite/docker/tree/ArgTest.java new file mode 100644 index 0000000000..b8aca019d7 --- /dev/null +++ b/rewrite-docker/src/test/java/org/openrewrite/docker/tree/ArgTest.java @@ -0,0 +1,90 @@ +/* + * Copyright 2026 the original author or authors. + *

+ * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * https://www.apache.org/licenses/LICENSE-2.0 + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.openrewrite.docker.tree; + +import org.junit.jupiter.api.Test; +import org.openrewrite.test.RewriteTest; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.openrewrite.docker.Assertions.docker; + +class ArgTest implements RewriteTest { + + @Test + void simpleArg() { + rewriteRun( + docker( + """ + FROM ubuntu:20.04 + ARG VERSION=1.0.0 + """, + spec -> spec.afterRecipe(doc -> { + Docker.Arg arg = (Docker.Arg) doc.getStages().getFirst().getInstructions().getLast(); + assertThat(arg.getName().getText()).isEqualTo("VERSION"); + assertThat(arg.getValue()).isNotNull(); + assertThat(((Docker.Literal) arg.getValue().getContents().getFirst()).getText()).isEqualTo("1.0.0"); + }) + ) + ); + } + + @Test + void argWithoutValue() { + rewriteRun( + docker( + """ + FROM ubuntu:20.04 + ARG VERSION + """, + spec -> spec.afterRecipe(doc -> { + Docker.Arg arg = (Docker.Arg) doc.getStages().getFirst().getInstructions().getLast(); + assertThat(arg.getName().getText()).isEqualTo("VERSION"); + assertThat(arg.getValue()).isNull(); + }) + ) + ); + } + + @Test + void argInstructions() { + rewriteRun( + docker( + """ + ARG BASE_IMAGE=ubuntu:20.04 + FROM ${BASE_IMAGE} + ARG VERSION + """ + ) + ); + } + + @Test + void globalArg() { + rewriteRun( + docker( + """ + ARG VERSION=25 + FROM ubuntu:${VERSION} + """, + spec -> spec.afterRecipe(doc -> { + assertThat(doc.getGlobalArgs()).hasSize(1); + Docker.Arg globalArg = doc.getGlobalArgs().getFirst(); + assertThat(globalArg.getName().getText()).isEqualTo("VERSION"); + }) + ) + ); + } +} diff --git a/rewrite-docker/src/test/java/org/openrewrite/docker/tree/CmdTest.java b/rewrite-docker/src/test/java/org/openrewrite/docker/tree/CmdTest.java new file mode 100644 index 0000000000..e4835b102d --- /dev/null +++ b/rewrite-docker/src/test/java/org/openrewrite/docker/tree/CmdTest.java @@ -0,0 +1,73 @@ +/* + * Copyright 2026 the original author or authors. + *

+ * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * https://www.apache.org/licenses/LICENSE-2.0 + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.openrewrite.docker.tree; + +import org.junit.jupiter.api.Test; +import org.openrewrite.test.RewriteTest; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.openrewrite.docker.Assertions.docker; + +class CmdTest implements RewriteTest { + + @Test + void cmdShellForm() { + rewriteRun( + docker( + """ + FROM ubuntu:20.04 + CMD nginx -g daemon off; + """, + spec -> spec.afterRecipe(doc -> { + var cmd = (Docker.Cmd) doc.getStages().getFirst().getInstructions().getLast(); + var shellForm = (Docker.ShellForm) cmd.getCommand(); + assertThat(shellForm.getArgument().getText()).isEqualTo("nginx -g daemon off;"); + }) + ) + ); + } + + @Test + void cmdExecForm() { + rewriteRun( + docker( + """ + FROM ubuntu:20.04 + CMD ["nginx", "-g", "daemon off;"] + """, + spec -> spec.afterRecipe(doc -> { + var cmd = (Docker.Cmd) doc.getStages().getFirst().getInstructions().getLast(); + assertThat(cmd.getCommand()).isInstanceOf(Docker.ExecForm.class); + var execForm = (Docker.ExecForm) cmd.getCommand(); + assertThat(execForm.getArguments()).hasSize(3); + }) + ) + ); + } + + @Test + void execFormWithSpacesBeforeClosingBracket() { + // CMD with space before closing bracket: CMD [ "/bin/bash" ] + rewriteRun( + docker( + """ + FROM ubuntu:20.04 + CMD [ "/bin/bash" ] + """ + ) + ); + } +} diff --git a/rewrite-docker/src/test/java/org/openrewrite/docker/tree/CommentTest.java b/rewrite-docker/src/test/java/org/openrewrite/docker/tree/CommentTest.java new file mode 100644 index 0000000000..4c16c1dadf --- /dev/null +++ b/rewrite-docker/src/test/java/org/openrewrite/docker/tree/CommentTest.java @@ -0,0 +1,94 @@ +/* + * Copyright 2026 the original author or authors. + *

+ * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * https://www.apache.org/licenses/LICENSE-2.0 + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.openrewrite.docker.tree; + +import org.junit.jupiter.api.Test; +import org.openrewrite.test.RewriteTest; + +import static org.openrewrite.docker.Assertions.docker; + +class CommentTest implements RewriteTest { + + @Test + void commentsAtTop() { + rewriteRun( + docker( + """ + # This is a comment + # Another comment line + FROM ubuntu:20.04 + """ + ) + ); + } + + @Test + void commentsInline() { + rewriteRun( + docker( + """ + FROM ubuntu:20.04 # Base image + RUN apt-get update # Update packages + """ + ) + ); + } + + @Test + void commentsBetweenInstructions() { + rewriteRun( + docker( + """ + FROM ubuntu:20.04 + # Update and install dependencies + RUN apt-get update + # Install curl + RUN apt-get install -y curl + """ + ) + ); + } + + @Test + void emptyLinesAndComments() { + rewriteRun( + docker( + """ + # Base image + FROM ubuntu:20.04 + + # System updates + RUN apt-get update + + # Install packages + RUN apt-get install -y curl wget + """ + ) + ); + } + + @Test + void trailingCommentAfterImage() { + rewriteRun( + docker( + """ + FROM 'ubuntu:22.04' # Trailing comment + RUN apt-get update + """ + ) + ); + } +} diff --git a/rewrite-docker/src/test/java/org/openrewrite/docker/tree/CopyTest.java b/rewrite-docker/src/test/java/org/openrewrite/docker/tree/CopyTest.java new file mode 100644 index 0000000000..cc30e41b93 --- /dev/null +++ b/rewrite-docker/src/test/java/org/openrewrite/docker/tree/CopyTest.java @@ -0,0 +1,308 @@ +/* + * Copyright 2026 the original author or authors. + *

+ * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * https://www.apache.org/licenses/LICENSE-2.0 + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.openrewrite.docker.tree; + +import org.junit.jupiter.api.Test; +import org.openrewrite.test.RewriteTest; + +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.openrewrite.docker.Assertions.docker; + +class CopyTest implements RewriteTest { + + @Test + void simpleCopy() { + rewriteRun( + docker( + """ + FROM ubuntu:20.04 + COPY app.jar /app/ + """, + spec -> spec.afterRecipe(doc -> { + var copy = (Docker.Copy) doc.getStages().getFirst().getInstructions().getLast(); + assertThat(copy.getShellForm()).isNotNull(); + assertThat(copy.getShellForm().getSources()).hasSize(1); + assertThat(((Docker.Literal) copy.getShellForm().getSources().get(0).getContents().getFirst()).getText()).isEqualTo("app.jar"); + assertThat(((Docker.Literal) copy.getShellForm().getDestination().getContents().getFirst()).getText()).isEqualTo("/app/"); + }) + ) + ); + } + + @Test + void copyWithFlags() { + rewriteRun( + docker( + """ + FROM ubuntu:20.04 + COPY --chown=app:app app.jar /app/ + COPY --from=builder /build/output /app/ + """, + spec -> spec.afterRecipe(doc -> { + List instructions = doc.getStages().getFirst().getInstructions(); + var copy1 = (Docker.Copy) instructions.getFirst(); + assertThat(copy1.getFlags()).hasSize(1); + assertThat(copy1.getFlags().getFirst().getName()).isEqualTo("chown"); + assertThat(((Docker.Literal) copy1.getFlags().getFirst().getValue().getContents().getFirst()).getText()).isEqualTo("app:app"); + assertThat(copy1.getShellForm()).isNotNull(); + var source = (Docker.Literal) copy1.getShellForm().getSources().get(0).getContents().getFirst(); + assertThat(source.getText()).isEqualTo("app.jar"); + var destination = (Docker.Literal) copy1.getShellForm().getDestination().getContents().getFirst(); + assertThat(destination.getText()).isEqualTo("/app/"); + + var copy2 = (Docker.Copy) instructions.getLast(); + assertThat(copy2.getFlags()).hasSize(1); + assertThat(copy2.getFlags().getFirst().getName()).isEqualTo("from"); + assertThat(((Docker.Literal) copy2.getFlags().getFirst().getValue().getContents().getFirst()).getText()).isEqualTo("builder"); + }) + ) + ); + } + + @Test + void copyWithHeredoc() { + rewriteRun( + docker( + """ + FROM ubuntu:20.04 + COPY < spec.afterRecipe(doc -> { + var copy = (Docker.Copy) doc.getStages().getFirst().getInstructions().getLast(); + assertThat(copy.getShellForm()).isNotNull(); + // Should have 3 separate source arguments, not 1 combined argument + assertThat(copy.getShellForm().getSources()).hasSize(3); + assertThat(((Docker.Literal) copy.getShellForm().getSources().get(0).getContents().getFirst()).getText()).isEqualTo("file1.txt"); + assertThat(((Docker.Literal) copy.getShellForm().getSources().get(1).getContents().getFirst()).getText()).isEqualTo("file2.txt"); + assertThat(((Docker.Literal) copy.getShellForm().getSources().get(2).getContents().getFirst()).getText()).isEqualTo("file3.txt"); + assertThat(((Docker.Literal) copy.getShellForm().getDestination().getContents().getFirst()).getText()).isEqualTo("/app/"); + }) + ) + ); + } + + @Test + void copyWithMultipleSourcesAndFlags() { + // COPY with flags and multiple source files + rewriteRun( + docker( + """ + FROM ubuntu:20.04 + COPY --chown=app:app config1.yaml config2.yaml /etc/app/ + """, + spec -> spec.afterRecipe(doc -> { + var copy = (Docker.Copy) doc.getStages().getFirst().getInstructions().getLast(); + assertThat(copy.getFlags()).hasSize(1); + assertThat(copy.getFlags().getFirst().getName()).isEqualTo("chown"); + assertThat(copy.getShellForm()).isNotNull(); + assertThat(copy.getShellForm().getSources()).hasSize(2); + assertThat(((Docker.Literal) copy.getShellForm().getSources().get(0).getContents().getFirst()).getText()).isEqualTo("config1.yaml"); + assertThat(((Docker.Literal) copy.getShellForm().getSources().get(1).getContents().getFirst()).getText()).isEqualTo("config2.yaml"); + }) + ) + ); + } + + @Test + void copyHeredocEofInOpeningNotDestination() { + // Verify that the EOF marker is in the HeredocForm.opening field, not in destination + rewriteRun( + docker( + """ + FROM ubuntu:20.04 + COPY < spec.afterRecipe(doc -> { + var copy = (Docker.Copy) doc.getStages().getFirst().getInstructions().getLast(); + assertThat(copy.getHeredoc()).isNotNull(); + + // The preamble should contain "< spec.afterRecipe(doc -> { + var copy = (Docker.Copy) doc.getStages().getFirst().getInstructions().getLast(); + assertThat(copy.getHeredoc()).isNotNull(); + assertThat(copy.getHeredoc().getPreamble()).isEqualTo("< spec.afterRecipe(doc -> { + var copy = (Docker.Copy) doc.getStages().getFirst().getInstructions().getLast(); + + // Verify exec form is used (not shell form or heredoc) + assertThat(copy.getExecForm()).isNotNull(); + assertThat(copy.getShellForm()).isNull(); + assertThat(copy.getHeredoc()).isNull(); + + // Get the arguments from exec form + var args = copy.getExecForm().getArguments(); + + // Should have 4 arguments: 3 sources + 1 destination + assertThat(args).hasSize(4); + + // Verify each argument is a quoted literal with correct text + // First source + assertThat(args.get(0).getText()).isEqualTo("src/file1.txt"); + assertThat(args.get(0).getQuoteStyle()).isEqualTo(Docker.Literal.QuoteStyle.DOUBLE); + + // Second source + assertThat(args.get(1).getText()).isEqualTo("src/file2.txt"); + assertThat(args.get(1).getQuoteStyle()).isEqualTo(Docker.Literal.QuoteStyle.DOUBLE); + + // Third source + assertThat(args.get(2).getText()).isEqualTo("config.yaml"); + assertThat(args.get(2).getQuoteStyle()).isEqualTo(Docker.Literal.QuoteStyle.DOUBLE); + + // Destination (last element) + assertThat(args.get(3).getText()).isEqualTo("/app/"); + assertThat(args.get(3).getQuoteStyle()).isEqualTo(Docker.Literal.QuoteStyle.DOUBLE); + }) + ) + ); + } + + @Test + void copyExecFormWithFlagsAndMultipleSources() { + // COPY exec form with --from flag and multiple sources + rewriteRun( + docker( + """ + FROM ubuntu:20.04 + COPY --from=builder ["bin/app", "lib/helper.so", "/usr/local/bin/"] + """, + spec -> spec.afterRecipe(doc -> { + var copy = (Docker.Copy) doc.getStages().getFirst().getInstructions().getLast(); + + // Verify flags + assertThat(copy.getFlags()).hasSize(1); + assertThat(copy.getFlags().getFirst().getName()).isEqualTo("from"); + assertThat(((Docker.Literal) copy.getFlags().getFirst().getValue().getContents().getFirst()).getText()) + .isEqualTo("builder"); + + // Verify exec form + assertThat(copy.getExecForm()).isNotNull(); + var args = copy.getExecForm().getArguments(); + assertThat(args).hasSize(3); + + // Sources + assertThat(args.get(0).getText()).isEqualTo("bin/app"); + assertThat(args.get(1).getText()).isEqualTo("lib/helper.so"); + + // Destination + assertThat(args.get(2).getText()).isEqualTo("/usr/local/bin/"); + }) + ) + ); + } +} diff --git a/rewrite-docker/src/test/java/org/openrewrite/docker/tree/EntrypointTest.java b/rewrite-docker/src/test/java/org/openrewrite/docker/tree/EntrypointTest.java new file mode 100644 index 0000000000..5ae7ee470c --- /dev/null +++ b/rewrite-docker/src/test/java/org/openrewrite/docker/tree/EntrypointTest.java @@ -0,0 +1,74 @@ +/* + * Copyright 2026 the original author or authors. + *

+ * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * https://www.apache.org/licenses/LICENSE-2.0 + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.openrewrite.docker.tree; + +import org.junit.jupiter.api.Test; +import org.openrewrite.test.RewriteTest; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.openrewrite.docker.Assertions.docker; + +class EntrypointTest implements RewriteTest { + + @Test + void entrypointExecForm() { + rewriteRun( + docker( + """ + FROM ubuntu:20.04 + ENTRYPOINT ["./app"] + """, + spec -> spec.afterRecipe(doc -> { + Docker.Entrypoint entrypoint = (Docker.Entrypoint) doc.getStages().getFirst().getInstructions().getLast(); + assertThat(entrypoint.getCommand()).isInstanceOf(Docker.ExecForm.class); + Docker.ExecForm execForm = (Docker.ExecForm) entrypoint.getCommand(); + assertThat(execForm.getArguments()).hasSize(1); + }) + ) + ); + } + + @Test + void entrypointShellForm() { + rewriteRun( + docker( + """ + FROM ubuntu:20.04 + ENTRYPOINT /bin/sh -c 'echo hello' + """, + spec -> spec.afterRecipe(doc -> { + Docker.Entrypoint entrypoint = (Docker.Entrypoint) doc.getStages().getFirst().getInstructions().getLast(); + assertThat(entrypoint.getCommand()).isInstanceOf(Docker.ShellForm.class); + Docker.ShellForm shellForm = (Docker.ShellForm) entrypoint.getCommand(); + assertThat(shellForm.getArgument()).isNotNull(); + }) + ) + ); + } + + @Test + void entrypointWithSpacesInJsonArray() { + // ENTRYPOINT with spaces inside JSON array + rewriteRun( + docker( + """ + FROM ubuntu:20.04 + ENTRYPOINT [ "/entrypoint.sh" ] + """ + ) + ); + } +} diff --git a/rewrite-docker/src/test/java/org/openrewrite/docker/tree/EnvTest.java b/rewrite-docker/src/test/java/org/openrewrite/docker/tree/EnvTest.java new file mode 100644 index 0000000000..87f17b5291 --- /dev/null +++ b/rewrite-docker/src/test/java/org/openrewrite/docker/tree/EnvTest.java @@ -0,0 +1,100 @@ +/* + * Copyright 2026 the original author or authors. + *

+ * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * https://www.apache.org/licenses/LICENSE-2.0 + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.openrewrite.docker.tree; + +import org.junit.jupiter.api.Test; +import org.openrewrite.test.RewriteTest; + +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.openrewrite.docker.Assertions.docker; + +class EnvTest implements RewriteTest { + + @Test + void envSingleLine() { + rewriteRun( + docker( + """ + FROM ubuntu:20.04 + ENV NODE_VERSION=18.0.0 + ENV PATH=/usr/local/bin:$PATH + """, + spec -> spec.afterRecipe(doc -> { + List instructions = doc.getStages().getFirst().getInstructions(); + Docker.Env env1 = (Docker.Env) instructions.getFirst(); + assertThat(env1.getPairs()).hasSize(1); + assertThat(env1.getPairs().getFirst().getKey().getText()).isEqualTo("NODE_VERSION"); + assertThat(((Docker.Literal) env1.getPairs().getFirst().getValue().getContents().getFirst()).getText()).isEqualTo("18.0.0"); + }) + ) + ); + } + + @Test + void envMultiplePairs() { + rewriteRun( + docker( + """ + FROM ubuntu:20.04 + ENV NODE_VERSION=18.0.0 NPM_VERSION=9.0.0 + """, + spec -> spec.afterRecipe(doc -> { + Docker.Env env = (Docker.Env) doc.getStages().getFirst().getInstructions().getLast(); + assertThat(env.getPairs()).hasSize(2); + assertThat(env.getPairs().get(0).getKey().getText()).isEqualTo("NODE_VERSION"); + assertThat(((Docker.Literal) env.getPairs().get(0).getValue().getContents().getFirst()).getText()).isEqualTo("18.0.0"); + assertThat(env.getPairs().get(1).getKey().getText()).isEqualTo("NPM_VERSION"); + assertThat(((Docker.Literal) env.getPairs().get(1).getValue().getContents().getFirst()).getText()).isEqualTo("9.0.0"); + }) + ) + ); + } + + @Test + void envOldStyleSpaceSeparated() { + rewriteRun( + docker( + """ + FROM ubuntu:20.04 + ENV NODE_VERSION 18.0.0 + """, + spec -> spec.afterRecipe(doc -> { + List instructions = doc.getStages().getFirst().getInstructions(); + Docker.Env env1 = (Docker.Env) instructions.getFirst(); + assertThat(env1.getPairs()).hasSize(1); + assertThat(env1.getPairs().getFirst().isHasEquals()).isFalse(); + assertThat(env1.getPairs().getFirst().getKey().getText()).isEqualTo("NODE_VERSION"); + assertThat(((Docker.Literal) env1.getPairs().getFirst().getValue().getContents().getFirst()).getText()).isEqualTo("18.0.0"); + }) + ) + ); + } + + @Test + void envWithKeywordName() { + // Test ENV where key is a Docker keyword (like SHELL) + rewriteRun( + docker( + """ + FROM alpine:latest + ENV SHELL /usr/bin/zsh + """ + ) + ); + } +} diff --git a/rewrite-docker/src/test/java/org/openrewrite/docker/tree/ExposeTest.java b/rewrite-docker/src/test/java/org/openrewrite/docker/tree/ExposeTest.java new file mode 100644 index 0000000000..5b0a8f4510 --- /dev/null +++ b/rewrite-docker/src/test/java/org/openrewrite/docker/tree/ExposeTest.java @@ -0,0 +1,156 @@ +/* + * Copyright 2026 the original author or authors. + *

+ * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * https://www.apache.org/licenses/LICENSE-2.0 + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.openrewrite.docker.tree; + +import org.junit.jupiter.api.Test; +import org.openrewrite.test.RewriteTest; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.openrewrite.docker.Assertions.docker; + +class ExposeTest implements RewriteTest { + + @Test + void exposeInstruction() { + rewriteRun( + docker( + """ + FROM ubuntu:20.04 + EXPOSE 8080 + """, + spec -> spec.afterRecipe(doc -> { + Docker.Expose expose = (Docker.Expose) doc.getStages().getFirst().getInstructions().getLast(); + assertThat(expose.getPorts()).hasSize(1); + Docker.Port port = expose.getPorts().getFirst(); + assertThat(port.getText()).isEqualTo("8080"); + assertThat(port.getStart()).isEqualTo(8080); + assertThat(port.getEnd()).isNull(); + assertThat(port.getProtocol()).isEqualTo(Docker.Port.Protocol.TCP); + }) + ) + ); + } + + @Test + void exposeMultiplePorts() { + rewriteRun( + docker( + """ + FROM ubuntu:20.04 + EXPOSE 8080 8443 + """, + spec -> spec.afterRecipe(doc -> { + Docker.Expose expose = (Docker.Expose) doc.getStages().getFirst().getInstructions().getLast(); + assertThat(expose.getPorts()).hasSize(2); + assertThat(expose.getPorts().get(0).getText()).isEqualTo("8080"); + assertThat(expose.getPorts().get(0).getStart()).isEqualTo(8080); + assertThat(expose.getPorts().get(1).getText()).isEqualTo("8443"); + assertThat(expose.getPorts().get(1).getStart()).isEqualTo(8443); + }) + ) + ); + } + + @Test + void exposeWithEnvironmentVariable() { + // EXPOSE with environment variable reference + rewriteRun( + docker( + """ + FROM ubuntu:20.04 + ENV PORT=8080 + EXPOSE ${PORT} + """, + spec -> spec.afterRecipe(doc -> { + Docker.Expose expose = (Docker.Expose) doc.getStages().getFirst().getInstructions().getLast(); + Docker.Port port = expose.getPorts().getFirst(); + assertThat(port.getText()).isEqualTo("${PORT}"); + assertThat(port.isVariable()).isTrue(); + assertThat(port.getStart()).isNull(); + assertThat(port.getEnd()).isNull(); + }) + ) + ); + } + + @Test + void exposeWithProtocol() { + rewriteRun( + docker( + """ + FROM ubuntu:20.04 + EXPOSE 8080/tcp 53/udp + """, + spec -> spec.afterRecipe(doc -> { + Docker.Expose expose = (Docker.Expose) doc.getStages().getFirst().getInstructions().getLast(); + assertThat(expose.getPorts()).hasSize(2); + + Docker.Port tcpPort = expose.getPorts().get(0); + assertThat(tcpPort.getText()).isEqualTo("8080/tcp"); + assertThat(tcpPort.getStart()).isEqualTo(8080); + assertThat(tcpPort.getProtocol()).isEqualTo(Docker.Port.Protocol.TCP); + + Docker.Port udpPort = expose.getPorts().get(1); + assertThat(udpPort.getText()).isEqualTo("53/udp"); + assertThat(udpPort.getStart()).isEqualTo(53); + assertThat(udpPort.getProtocol()).isEqualTo(Docker.Port.Protocol.UDP); + }) + ) + ); + } + + @Test + void exposePortRange() { + rewriteRun( + docker( + """ + FROM ubuntu:20.04 + EXPOSE 8000-9000 + """, + spec -> spec.afterRecipe(doc -> { + Docker.Expose expose = (Docker.Expose) doc.getStages().getFirst().getInstructions().getLast(); + Docker.Port port = expose.getPorts().getFirst(); + assertThat(port.getText()).isEqualTo("8000-9000"); + assertThat(port.getStart()).isEqualTo(8000); + assertThat(port.getEnd()).isEqualTo(9000); + assertThat(port.isRange()).isTrue(); + assertThat(port.getProtocol()).isEqualTo(Docker.Port.Protocol.TCP); + }) + ) + ); + } + + @Test + void exposePortRangeWithProtocol() { + rewriteRun( + docker( + """ + FROM ubuntu:20.04 + EXPOSE 8000-9000/udp + """, + spec -> spec.afterRecipe(doc -> { + Docker.Expose expose = (Docker.Expose) doc.getStages().getFirst().getInstructions().getLast(); + Docker.Port port = expose.getPorts().getFirst(); + assertThat(port.getText()).isEqualTo("8000-9000/udp"); + assertThat(port.getStart()).isEqualTo(8000); + assertThat(port.getEnd()).isEqualTo(9000); + assertThat(port.isRange()).isTrue(); + assertThat(port.getProtocol()).isEqualTo(Docker.Port.Protocol.UDP); + }) + ) + ); + } +} diff --git a/rewrite-docker/src/test/java/org/openrewrite/docker/tree/FromTest.java b/rewrite-docker/src/test/java/org/openrewrite/docker/tree/FromTest.java new file mode 100644 index 0000000000..099c02d705 --- /dev/null +++ b/rewrite-docker/src/test/java/org/openrewrite/docker/tree/FromTest.java @@ -0,0 +1,196 @@ +/* + * Copyright 2026 the original author or authors. + *

+ * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * https://www.apache.org/licenses/LICENSE-2.0 + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.openrewrite.docker.tree; + +import org.junit.jupiter.api.Test; +import org.openrewrite.test.RewriteTest; + +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.openrewrite.docker.Assertions.docker; + +class FromTest implements RewriteTest { + + @Test + void simpleFrom() { + rewriteRun( + docker( + """ + FROM ubuntu:20.04 + """, + spec -> spec.afterRecipe(doc -> { + Docker.From from = doc.getStages().getFirst().getFrom(); + assertThat(((Docker.Literal) from.getImageName().getContents().getFirst()).getText()).isEqualTo("ubuntu"); + assertThat(((Docker.Literal) from.getTag().getContents().getFirst()).getText()).isEqualTo("20.04"); + assertThat(from.getDigest()).isNull(); + assertThat(from.getAs()).isNull(); + }) + ) + ); + } + + @Test + void fromWithPlatform() { + rewriteRun( + docker( + """ + FROM --platform=linux/amd64 ubuntu:20.04 + """, + spec -> spec.afterRecipe(doc -> { + Docker.From from = doc.getStages().getFirst().getFrom(); + assertThat(from.getFlags()).hasSize(1); + assertThat(from.getFlags().getFirst().getName()).isEqualTo("platform"); + assertThat(((Docker.Literal) from.getFlags().getFirst().getValue().getContents().getFirst()).getText()).isEqualTo("linux/amd64"); + assertThat(((Docker.Literal) from.getImageName().getContents().getFirst()).getText()).isEqualTo("ubuntu"); + assertThat(((Docker.Literal) from.getTag().getContents().getFirst()).getText()).isEqualTo("20.04"); + }) + ) + ); + } + + @Test + void fromWithAs() { + rewriteRun( + docker( + """ + FROM ubuntu:20.04 AS base + """, + spec -> spec.afterRecipe(doc -> { + Docker.From from = doc.getStages().getFirst().getFrom(); + assertThat(((Docker.Literal) from.getImageName().getContents().getFirst()).getText()).isEqualTo("ubuntu"); + assertThat(((Docker.Literal) from.getTag().getContents().getFirst()).getText()).isEqualTo("20.04"); + assertThat(from.getAs()).isNotNull(); + assertThat(from.getAs().getName().getText()).isEqualTo("base"); + }) + ) + ); + } + + @Test + void multiStageFrom() { + rewriteRun( + docker( + """ + FROM golang:1.20 AS builder + RUN go build -o app . + + FROM alpine:latest + RUN apk add --no-cache ca-certificates + """, + spec -> spec.afterRecipe(doc -> assertThat(doc.getStages()) + .satisfiesExactly( + golang -> assertThat(((Docker.Literal) golang.getFrom().getImageName().getContents().getFirst()).getText()).isEqualTo("golang"), + alpine -> assertThat(((Docker.Literal) alpine.getFrom().getImageName().getContents().getFirst()).getText()).isEqualTo("alpine") + )) + ) + ); + } + + @Test + void fromWithPlatformEnvVar() { + rewriteRun( + docker( + """ + FROM --platform=$BUILDPLATFORM node:18 AS builder + """, + spec -> spec.afterRecipe(doc -> { + Docker.From from = doc.getStages().getFirst().getFrom(); + assertThat(from.getFlags()).hasSize(1); + assertThat(from.getFlags().getFirst().getName()).isEqualTo("platform"); + assertThat(from.getFlags().getFirst().getValue().getContents().getFirst()) + .isInstanceOf(Docker.EnvironmentVariable.class); + Docker.EnvironmentVariable envVar = (Docker.EnvironmentVariable) from.getFlags().getFirst().getValue().getContents().getFirst(); + assertThat(envVar.getName()).isEqualTo("BUILDPLATFORM"); + }) + ) + ); + } + + @Test + void fromWithPlatformBracedEnvVar() { + rewriteRun( + docker( + """ + FROM --platform=${TARGETPLATFORM} alpine:latest + """, + spec -> spec.afterRecipe(doc -> { + Docker.From from = doc.getStages().getFirst().getFrom(); + assertThat(from.getFlags()).hasSize(1); + Docker.EnvironmentVariable envVar = (Docker.EnvironmentVariable) from.getFlags().getFirst().getValue().getContents().getFirst(); + assertThat(envVar.getName()).isEqualTo("TARGETPLATFORM"); + assertThat(envVar.isBraced()).isTrue(); + }) + ) + ); + } + + @Test + void complexExpression() { + rewriteRun( + docker( + """ + ARG VERSION=25 + FROM $REGISTRY/image:${VERSION}-suffix + """, + spec -> spec.afterRecipe(doc -> { + Docker.From from = doc.getStages().getLast().getFrom(); + + // Check imageName contents + List imageNameContents = from.getImageName().getContents(); + assertThat(imageNameContents).hasSize(2); + assertThat(imageNameContents.getFirst()).extracting(arg -> ((Docker.EnvironmentVariable) arg).getName()).isEqualTo("REGISTRY"); + assertThat(imageNameContents.get(1)).extracting(arg -> ((Docker.Literal) arg).getText()).isEqualTo("/image"); + + // Check tag contents + assertThat(from.getTag()).isNotNull(); + List tagContents = from.getTag().getContents(); + assertThat(tagContents).hasSize(2); + assertThat(tagContents.getFirst()).extracting(arg -> ((Docker.EnvironmentVariable) arg).getName()).isEqualTo("VERSION"); + assertThat(tagContents.get(1)).extracting(arg -> ((Docker.Literal) arg).getText()).isEqualTo("-suffix"); + + // Check no digest + assertThat(from.getDigest()).isNull(); + }) + ) + ); + } + + @Test + void lowercaseInstructions() { + rewriteRun( + docker( + """ + from ubuntu:20.04 + run apt-get update + """, + spec -> spec.afterRecipe(doc -> assertThat(doc.getStages().getFirst().getFrom().getKeyword()).isEqualTo("from")) + ) + ); + } + + @Test + void mixedCaseInstructions() { + rewriteRun( + docker( + """ + From ubuntu:20.04 as builder + Run apt-get update + """ + ) + ); + } +} diff --git a/rewrite-docker/src/test/java/org/openrewrite/docker/tree/HealthcheckTest.java b/rewrite-docker/src/test/java/org/openrewrite/docker/tree/HealthcheckTest.java new file mode 100644 index 0000000000..04938b90d8 --- /dev/null +++ b/rewrite-docker/src/test/java/org/openrewrite/docker/tree/HealthcheckTest.java @@ -0,0 +1,237 @@ +/* + * Copyright 2026 the original author or authors. + *

+ * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * https://www.apache.org/licenses/LICENSE-2.0 + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.openrewrite.docker.tree; + +import org.junit.jupiter.api.Test; +import org.openrewrite.test.RewriteTest; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.openrewrite.docker.Assertions.docker; +import static org.openrewrite.test.TypeValidation.all; + +class HealthcheckTest implements RewriteTest { + + @Test + void healthcheckNone() { + rewriteRun( + docker( + """ + FROM ubuntu:20.04 + HEALTHCHECK NONE + """, + spec -> spec.afterRecipe(doc -> { + Docker.Healthcheck healthcheck = (Docker.Healthcheck) doc.getStages().getFirst().getInstructions().getLast(); + assertThat(healthcheck.isNone()).isTrue(); + assertThat(healthcheck.getCmd()).isNull(); + }) + ) + ); + } + + @Test + void healthcheckWithCmd() { + rewriteRun( + docker( + """ + FROM ubuntu:20.04 + HEALTHCHECK CMD curl -f http://localhost/ || exit 1 + """, + spec -> spec.afterRecipe(doc -> { + Docker.Healthcheck healthcheck = (Docker.Healthcheck) doc.getStages().getFirst().getInstructions().getLast(); + assertThat(healthcheck.isNone()).isFalse(); + assertThat(healthcheck.getCmd()).isNotNull(); + var command = (Docker.ShellForm) healthcheck.getCmd().getCommand(); + assertThat(command.getArgument().getText()).isEqualTo("curl -f http://localhost/ || exit 1"); + }) + ) + ); + } + + @Test + void multiStageWithHealthcheckFollowedByCopy() { + // Test that HEALTHCHECK is correctly parsed when followed by COPY --from + rewriteRun( + docker( + """ + FROM golang:1.21 AS builder + RUN go build -o app . + + FROM alpine:3.18 + HEALTHCHECK CMD curl -f http://localhost/ || exit 1 + COPY --from=builder /app /app + """, + spec -> spec.afterRecipe(doc -> { + assertThat(doc.getStages()).hasSize(2); + // Final stage should have HEALTHCHECK as first instruction and COPY as second + Docker.Stage finalStage = doc.getStages().get(1); + assertThat(finalStage.getInstructions().getFirst()).isInstanceOf(Docker.Healthcheck.class); + assertThat(finalStage.getInstructions().getLast()).isInstanceOf(Docker.Copy.class); + }) + ) + ); + } + + @Test + void healthcheckWithIntervalFlag() { + rewriteRun( + docker( + """ + FROM ubuntu:20.04 + HEALTHCHECK --interval=30s CMD curl -f http://localhost/ || exit 1 + """, + spec -> spec.afterRecipe(doc -> { + var healthcheck = (Docker.Healthcheck) doc.getStages().getFirst().getInstructions().getLast(); + assertThat(healthcheck.isNone()).isFalse(); + assertThat(healthcheck.getFlags()).hasSize(1); + var flag = healthcheck.getFlags().getFirst(); + assertThat(flag.getName()).isEqualTo("interval"); + assertThat(flag.getValue()).isNotNull(); + assertThat(flag.getValue().getContents()).isNotEmpty(); + assertThat(((Docker.Literal) flag.getValue().getContents().getFirst()).getText()).isEqualTo("30s"); + }) + ) + ); + } + + @Test + void healthcheckWithAllFlags() { + rewriteRun( + docker( + """ + FROM ubuntu:20.04 + HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --start-interval=1s --retries=3 CMD curl -f http://localhost/ || exit 1 + """, + spec -> spec.afterRecipe(doc -> { + var healthcheck = (Docker.Healthcheck) doc.getStages().getFirst().getInstructions().getLast(); + assertThat(healthcheck.isNone()).isFalse(); + assertThat(healthcheck.getFlags()).hasSize(5); + assertThat(healthcheck.getFlags().get(0).getName()).isEqualTo("interval"); + assertThat(healthcheck.getFlags().get(1).getName()).isEqualTo("timeout"); + assertThat(healthcheck.getFlags().get(2).getName()).isEqualTo("start-period"); + assertThat(healthcheck.getFlags().get(3).getName()).isEqualTo("start-interval"); + assertThat(healthcheck.getFlags().get(4).getName()).isEqualTo("retries"); + var command = (Docker.ShellForm) healthcheck.getCmd().getCommand(); + assertThat(command.getArgument().getText()).isEqualTo("curl -f http://localhost/ || exit 1"); + }) + ) + ); + } + + @Test + void healthcheckWithLineContinuationInCommand() { + // Line continuation in the command itself (after CMD) + rewriteRun( + spec -> spec.typeValidationOptions(all().allowNonWhitespaceInWhitespace(true)), + docker( + """ + FROM ubuntu:20.04 + HEALTHCHECK --interval=5m --timeout=3s \\ + CMD curl -f http://localhost/ || exit 1 + """, + spec -> spec.afterRecipe(doc -> { + var healthcheck = (Docker.Healthcheck) doc.getStages().getFirst().getInstructions().getLast(); + + Docker.Flag interval = healthcheck.getFlags().getFirst(); + assertThat(interval.getName()).isEqualTo("interval"); + assertThat(((Docker.Literal)interval.getValue().getContents().getFirst()).getText()).isEqualTo("5m"); + + Docker.Flag timeout = healthcheck.getFlags().getLast(); + assertThat(timeout.getName()).isEqualTo("timeout"); + assertThat(((Docker.Literal)timeout.getValue().getContents().getFirst()).getText()).isEqualTo("3s"); + + var command = (Docker.ShellForm) healthcheck.getCmd().getCommand(); + assertThat(command.getArgument().getText()).isEqualTo("curl -f http://localhost/ || exit 1"); + }) + ) + ); + } + + @Test + void healthcheckWithExecForm() { + rewriteRun( + docker( + """ + FROM ubuntu:20.04 + HEALTHCHECK --interval=30s CMD ["curl", "-f", "http://localhost/"] + """, + spec -> spec.afterRecipe(doc -> { + var healthcheck = (Docker.Healthcheck) doc.getStages().getFirst().getInstructions().getLast(); + + Docker.Flag interval = healthcheck.getFlags().getFirst(); + assertThat(interval.getName()).isEqualTo("interval"); + assertThat(((Docker.Literal)interval.getValue().getContents().getFirst()).getText()).isEqualTo("30s"); + + assertThat(healthcheck.getCmd()).isNotNull(); + assertThat(healthcheck.getCmd().getCommand()).isInstanceOf(Docker.ExecForm.class); + var execForm = (Docker.ExecForm) healthcheck.getCmd().getCommand(); + assertThat(execForm.getArguments()).hasSize(3); + }) + ) + ); + } + + @Test + void healthcheckNoneWithMultipleSpaces() { + // HEALTHCHECK with multiple spaces between keyword and NONE + rewriteRun( + docker( + """ + FROM ubuntu:20.04 + HEALTHCHECK NONE + """ + ) + ); + } + + @Test + void healthcheckNoneWithTab() { + // HEALTHCHECK with tab between keyword and NONE + rewriteRun( + docker( + """ + FROM ubuntu:20.04 + HEALTHCHECK\tNONE + """ + ) + ); + } + + @Test + void healthcheckCmdWithMultipleSpaces() { + // HEALTHCHECK with multiple spaces between keyword and CMD + rewriteRun( + docker( + """ + FROM ubuntu:20.04 + HEALTHCHECK CMD curl -f http://localhost/ + """ + ) + ); + } + + @Test + void healthcheckCmdWithTab() { + // HEALTHCHECK with tab between keyword and CMD + rewriteRun( + docker( + """ + FROM ubuntu:20.04 + HEALTHCHECK\tCMD curl -f http://localhost/ + """ + ) + ); + } +} diff --git a/rewrite-docker/src/test/java/org/openrewrite/docker/tree/HeredocTest.java b/rewrite-docker/src/test/java/org/openrewrite/docker/tree/HeredocTest.java new file mode 100644 index 0000000000..0d95115ecc --- /dev/null +++ b/rewrite-docker/src/test/java/org/openrewrite/docker/tree/HeredocTest.java @@ -0,0 +1,122 @@ +/* + * Copyright 2026 the original author or authors. + *

+ * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * https://www.apache.org/licenses/LICENSE-2.0 + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.openrewrite.docker.tree; + +import org.junit.jupiter.api.Test; +import org.openrewrite.test.RewriteTest; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.openrewrite.docker.Assertions.docker; + +class HeredocTest implements RewriteTest { + + @Test + void heredocWithShellPath() { + rewriteRun( + docker( + """ + FROM ubuntu:20.04 + RUN <file1.sh &&\\ + <file2.sh &&\\ + chmod +x file1.sh file2.sh + #!/bin/bash + echo "script 1" + EOF1 + #!/bin/bash + echo "script 2" + EOF2 + """, + spec -> spec.afterRecipe(file -> { + var run = (Docker.Run) file.getStages().getFirst().getInstructions().getLast(); + + // Verify the command is a HeredocForm + assertThat(run.getCommand()).isInstanceOf(Docker.HeredocForm.class); + var heredoc = (Docker.HeredocForm) run.getCommand(); + + // Verify the preamble contains heredoc markers and commands + String preamble = heredoc.getPreamble(); + assertThat(preamble).isEqualTo(""" + <file1.sh &&\\ + <file2.sh &&\\ + chmod +x file1.sh file2.sh\ + """); + + // Verify we have two heredoc bodies + assertThat(heredoc.getBodies()).hasSize(2); + + // Verify first heredoc body (EOF1) + var body1 = heredoc.getBodies().getFirst(); + assertThat(body1.getOpening()).isEqualTo("< line.contains("script 1")); + + // Verify second heredoc body (EOF2) + var body2 = heredoc.getBodies().getLast(); + assertThat(body2.getOpening()).isEqualTo("< line.contains("script 2")); + }) + ) + ); + } +} diff --git a/rewrite-docker/src/test/java/org/openrewrite/docker/tree/LabelTest.java b/rewrite-docker/src/test/java/org/openrewrite/docker/tree/LabelTest.java new file mode 100644 index 0000000000..e8f3c10c13 --- /dev/null +++ b/rewrite-docker/src/test/java/org/openrewrite/docker/tree/LabelTest.java @@ -0,0 +1,148 @@ +/* + * Copyright 2026 the original author or authors. + *

+ * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * https://www.apache.org/licenses/LICENSE-2.0 + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.openrewrite.docker.tree; + +import org.junit.jupiter.api.Test; +import org.openrewrite.test.RewriteTest; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.openrewrite.docker.Assertions.docker; + +class LabelTest implements RewriteTest { + + @Test + void labelSinglePair() { + rewriteRun( + docker( + """ + FROM ubuntu:20.04 + LABEL version=1.0.0 + """, + spec -> spec.afterRecipe(doc -> { + Docker.Label label = (Docker.Label) doc.getStages().getFirst().getInstructions().getLast(); + assertThat(label.getPairs()).hasSize(1); + assertThat(((Docker.Literal) label.getPairs().getFirst().getKey().getContents().getFirst()).getText()).isEqualTo("version"); + assertThat(((Docker.Literal) label.getPairs().getFirst().getValue().getContents().getFirst()).getText()).isEqualTo("1.0.0"); + }) + ) + ); + } + + @Test + void labelMultiplePairs() { + rewriteRun( + docker( + """ + FROM ubuntu:20.04 + LABEL version=1.0.0 app=myapp + """, + spec -> spec.afterRecipe(doc -> { + Docker.Label label = (Docker.Label) doc.getStages().getFirst().getInstructions().getLast(); + assertThat(label.getPairs()).hasSize(2); + assertThat(((Docker.Literal) label.getPairs().get(0).getKey().getContents().getFirst()).getText()).isEqualTo("version"); + assertThat(((Docker.Literal) label.getPairs().get(1).getKey().getContents().getFirst()).getText()).isEqualTo("app"); + }) + ) + ); + } + + @Test + void labelWithQuotedValues() { + rewriteRun( + docker( + """ + FROM ubuntu:20.04 + LABEL description="My application" version="1.0.0" + """ + ) + ); + } + + @Test + void labelOldFormatWithoutEquals() { + // Old-style LABEL format: key value (without equals sign) + rewriteRun( + docker( + """ + FROM ubuntu:20.04 + LABEL author John Doe + """, + spec -> spec.afterRecipe(doc -> { + Docker.Label label = (Docker.Label) doc.getStages().getFirst().getInstructions().getLast(); + assertThat(label.getPairs()).hasSize(1); + assertThat(label.getPairs().getFirst().isHasEquals()).isFalse(); + assertThat(((Docker.Literal) label.getPairs().getFirst().getKey().getContents().getFirst()).getText()).isEqualTo("author"); + }) + ) + ); + } + + @Test + void labelWithInstructionKeywordInValue() { + // LABEL value containing instruction keywords like "run" + rewriteRun( + docker( + """ + FROM ubuntu:20.04 + LABEL install.cmd /usr/bin/docker run -ti + """ + ) + ); + } + + @Test + void labelMixedFormats() { + // Multiple LABEL instructions with different formats + rewriteRun( + docker( + """ + FROM ubuntu:20.04 + LABEL version=1.0.0 + LABEL maintainer John Doe + LABEL description="My app" + """ + ) + ); + } + + @Test + void labelMaintainerKeyword() { + // LABEL with 'maintainer' as key (keyword used as label key) + rewriteRun( + docker( + """ + FROM alpine:latest + LABEL maintainer "Jessie Frazelle " + RUN apk add curl + """ + ) + ); + } + + @Test + void labelRunKeyword() { + // LABEL with 'RUN' as key (instruction keyword used as label key with equals) + rewriteRun( + docker( + """ + FROM centos:7 + LABEL RUN='/usr/bin/docker run -d --name myapp ${IMAGE}' + RUN yum install -y curl + """ + ) + ); + } +} diff --git a/rewrite-docker/src/test/java/org/openrewrite/docker/tree/MaintainerTest.java b/rewrite-docker/src/test/java/org/openrewrite/docker/tree/MaintainerTest.java new file mode 100644 index 0000000000..a6e2a4e654 --- /dev/null +++ b/rewrite-docker/src/test/java/org/openrewrite/docker/tree/MaintainerTest.java @@ -0,0 +1,41 @@ +/* + * Copyright 2026 the original author or authors. + *

+ * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * https://www.apache.org/licenses/LICENSE-2.0 + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.openrewrite.docker.tree; + +import org.junit.jupiter.api.Test; +import org.openrewrite.test.RewriteTest; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.openrewrite.docker.Assertions.docker; + +class MaintainerTest implements RewriteTest { + + @Test + void maintainerInstruction() { + rewriteRun( + docker( + """ + FROM ubuntu:20.04 + MAINTAINER John Doe + """, + spec -> spec.afterRecipe(doc -> { + Docker.Maintainer maintainer = (Docker.Maintainer) doc.getStages().getFirst().getInstructions().getFirst(); + assertThat(((Docker.Literal) maintainer.getText().getContents().getFirst()).getText()).isEqualTo("John Doe "); + }) + ) + ); + } +} diff --git a/rewrite-docker/src/test/java/org/openrewrite/docker/tree/OnbuildTest.java b/rewrite-docker/src/test/java/org/openrewrite/docker/tree/OnbuildTest.java new file mode 100644 index 0000000000..ada34c857b --- /dev/null +++ b/rewrite-docker/src/test/java/org/openrewrite/docker/tree/OnbuildTest.java @@ -0,0 +1,57 @@ +/* + * Copyright 2026 the original author or authors. + *

+ * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * https://www.apache.org/licenses/LICENSE-2.0 + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.openrewrite.docker.tree; + +import org.junit.jupiter.api.Test; +import org.openrewrite.test.RewriteTest; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.openrewrite.docker.Assertions.docker; + +class OnbuildTest implements RewriteTest { + + @Test + void onbuildInstruction() { + rewriteRun( + docker( + """ + FROM ubuntu:20.04 + ONBUILD RUN apt-get update + """, + spec -> spec.afterRecipe(doc -> { + Docker.Onbuild onbuild = (Docker.Onbuild) doc.getStages().getFirst().getInstructions().getLast(); + assertThat(onbuild.getInstruction()).isInstanceOf(Docker.Run.class); + }) + ) + ); + } + + @Test + void onbuildWithCopy() { + rewriteRun( + docker( + """ + FROM ubuntu:20.04 + ONBUILD COPY app.jar /app/ + """, + spec -> spec.afterRecipe(doc -> { + Docker.Onbuild onbuild = (Docker.Onbuild) doc.getStages().getFirst().getInstructions().getLast(); + assertThat(onbuild.getInstruction()).isInstanceOf(Docker.Copy.class); + }) + ) + ); + } +} diff --git a/rewrite-docker/src/test/java/org/openrewrite/docker/tree/RunTest.java b/rewrite-docker/src/test/java/org/openrewrite/docker/tree/RunTest.java new file mode 100644 index 0000000000..892841f418 --- /dev/null +++ b/rewrite-docker/src/test/java/org/openrewrite/docker/tree/RunTest.java @@ -0,0 +1,554 @@ +/* + * Copyright 2026 the original author or authors. + *

+ * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * https://www.apache.org/licenses/LICENSE-2.0 + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.openrewrite.docker.tree; + +import org.junit.jupiter.api.Test; +import org.openrewrite.test.RewriteTest; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.openrewrite.docker.Assertions.docker; + +class RunTest implements RewriteTest { + + @Test + void simpleRun() { + rewriteRun( + docker( + """ + FROM ubuntu:20.04 + RUN apt-get update + """, + spec -> spec.afterRecipe(doc -> { + Docker.Run run = (Docker.Run) doc.getStages().getFirst().getInstructions().getLast(); + assertThat(run.getCommand()).isInstanceOf(Docker.ShellForm.class); + Docker.ShellForm shellForm = (Docker.ShellForm) run.getCommand(); + assertThat(shellForm.getArgument().getText()).isEqualTo("apt-get update"); + }) + ) + ); + } + + @Test + void runExecForm() { + rewriteRun( + docker( + """ + FROM ubuntu:20.04 + RUN ["apt-get", "update"] + """, + spec -> spec.afterRecipe(doc -> { + Docker.Run run = (Docker.Run) doc.getStages().getFirst().getInstructions().getLast(); + assertThat(run.getCommand()).isInstanceOf(Docker.ExecForm.class); + Docker.ExecForm execForm = (Docker.ExecForm) run.getCommand(); + assertThat(execForm.getArguments()).hasSize(2) + .satisfiesExactly( + arg -> assertThat(arg.getText()).isEqualTo("apt-get"), + arg -> assertThat(arg.getText()).isEqualTo("update") + ); + }) + ) + ); + } + + @Test + void runWithEnvironmentVariable() { + rewriteRun( + docker( + """ + FROM ubuntu:20.04 + RUN CGO_ENABLED=0 go build -o app + """, + spec -> spec.afterRecipe(doc -> { + Docker.Run run = (Docker.Run) doc.getStages().getLast().getInstructions().getFirst(); + assertThat(run.getFlags()).isNull(); + assertThat(run.getCommand()).isInstanceOf(Docker.ShellForm.class); + Docker.ShellForm shellForm = (Docker.ShellForm) run.getCommand(); + assertThat(shellForm.getArgument().getText()).isEqualTo("CGO_ENABLED=0 go build -o app"); + }) + ) + ); + } + + @Test + void runWithLineContinuation() { + rewriteRun( + docker( + """ + FROM ubuntu:20.04 + RUN apt-get update && \\ + apt-get install -y curl && \\ + rm -rf /var/lib/apt/lists/* + """ + ) + ); + } + + @Test + void runWithLineContinuationAndConditional() { + rewriteRun( + docker( + """ + FROM ubuntu:20.04 + COPY app.jar /app/ + RUN if [ -d /app ]; then \\ + echo "App directory exists"; \\ + else \\ + mkdir -p /app; \\ + fi + """, + spec -> spec.afterRecipe(file -> { + var run = (Docker.Run) file.getStages().getFirst().getInstructions().getLast(); + var form = (Docker.CommandForm.ShellForm) run.getCommand(); + assertThat(form.getArgument().getText()).isEqualTo(""" + if [ -d /app ]; then \\ + echo "App directory exists"; \\ + else \\ + mkdir -p /app; \\ + fi\ + """); + }) + ) + ); + } + + @Test + void runWithConditional() { + rewriteRun( + docker( + """ + ARG VERSION + FROM golang AS backend + RUN if [[ -z "$VERSION" ]] ; then make heedy ; else make heedy VERSION=$VERSION ; fi + """, + spec -> spec.afterRecipe(file -> { + var run = (Docker.Run) file.getStages().getFirst().getInstructions().getLast(); + var form = (Docker.CommandForm.ShellForm) run.getCommand(); + assertThat(form.getArgument().getText()).isEqualTo("if [[ -z \"$VERSION\" ]] ; then make heedy ; else make heedy VERSION=$VERSION ; fi"); + }) + ) + ); + } + + @Test + void runWithSimpleFlag() { + rewriteRun( + docker( + """ + FROM ubuntu:20.04 + RUN --network=none apt-get update + """, + spec -> spec.afterRecipe(doc -> { + Docker.Run run = (Docker.Run) doc.getStages().getFirst().getInstructions().getLast(); + assertThat(run.getFlags()).hasSize(1); + assertThat(run.getFlags().getFirst().getName()).isEqualTo("network"); + assertThat(((Docker.Literal) run.getFlags().getFirst().getValue().getContents().getFirst()).getText()).contains("none"); + }) + ) + ); + } + + @Test + void runWithMultipleFlags() { + rewriteRun( + docker( + """ + FROM ubuntu:20.04 + RUN --network=none --mount=type=cache,target=/cache apt-get update + """, + spec -> spec.afterRecipe(doc -> { + Docker.Run run = (Docker.Run) doc.getStages().getFirst().getInstructions().getLast(); + assertThat(run.getFlags()).hasSize(2); + assertThat(run.getFlags().get(0).getName()).isEqualTo("network"); + assertThat(((Docker.Literal) run.getFlags().get(0).getValue().getContents().getFirst()).getText()).isEqualTo("none"); + assertThat(run.getFlags().get(1).getName()).isEqualTo("mount"); + // Flag value is parsed as multiple elements: type, =, cache,target, =, /cache + Docker.Argument mountValue = run.getFlags().get(1).getValue(); + assertThat(mountValue.getContents()).hasSize(5); + assertThat(((Docker.Literal) mountValue.getContents().get(0)).getText()).isEqualTo("type"); + }) + ) + ); + } + + @Test + void runWithHeredoc() { + rewriteRun( + docker( + """ + FROM ubuntu:20.04 + RUN < spec.afterRecipe(doc -> { + Docker.Run run = (Docker.Run) doc.getStages().getFirst().getInstructions().getLast(); + Docker.ShellForm shellForm = (Docker.ShellForm) run.getCommand(); + // The entire command including 'as user' should be preserved + assertThat(shellForm.getArgument().getText()).isEqualTo("useradd user && su - user -c \"whoami\" as user"); + }) + ) + ); + } + + @Test + void runWithAsInFilename() { + // Test 'as' appearing in a filename + rewriteRun( + docker( + """ + FROM ubuntu:20.04 + RUN ./install-as-root.sh + """, + spec -> spec.afterRecipe(doc -> { + Docker.Run run = (Docker.Run) doc.getStages().getFirst().getInstructions().getLast(); + Docker.ShellForm shellForm = (Docker.ShellForm) run.getCommand(); + assertThat(shellForm.getArgument().getText()).isEqualTo("./install-as-root.sh"); + }) + ) + ); + } + + @Test + void runWithCmdInShellCommand() { + // Test 'cmd' appearing in shell commands (Windows cmd.exe) + rewriteRun( + docker( + """ + FROM mcr.microsoft.com/windows/servercore:ltsc2022 + RUN cmd /c echo Hello World + """, + spec -> spec.afterRecipe(doc -> { + Docker.Run run = (Docker.Run) doc.getStages().getFirst().getInstructions().getLast(); + Docker.ShellForm shellForm = (Docker.ShellForm) run.getCommand(); + assertThat(shellForm.getArgument().getText()).isEqualTo("cmd /c echo Hello World"); + }) + ) + ); + } + + @Test + void runWithNoneInShellCommand() { + // Test 'none' appearing in shell commands + rewriteRun( + docker( + """ + FROM ubuntu:20.04 + RUN echo "Setting value to none" && export VAL=none + """, + spec -> spec.afterRecipe(doc -> { + Docker.Run run = (Docker.Run) doc.getStages().getFirst().getInstructions().getLast(); + Docker.ShellForm shellForm = (Docker.ShellForm) run.getCommand(); + assertThat(shellForm.getArgument().getText()).isEqualTo("echo \"Setting value to none\" && export VAL=none"); + }) + ) + ); + } + + @Test + void execFormCommasNotInPrefix() { + // Verify that commas are NOT stored in the element prefix - they are printed explicitly + rewriteRun( + docker( + """ + FROM ubuntu:20.04 + RUN ["apt-get", "update", "-y"] + """, + spec -> spec.afterRecipe(doc -> { + Docker.Run run = (Docker.Run) doc.getStages().getFirst().getInstructions().getLast(); + assertThat(run.getCommand()).isInstanceOf(Docker.ExecForm.class); + Docker.ExecForm execForm = (Docker.ExecForm) run.getCommand(); + assertThat(execForm.getArguments()).hasSize(3); + + // First argument has no comma in its prefix (it's after the opening bracket) + Docker.Literal first = execForm.getArguments().get(0); + assertThat(first.getPrefix().getWhitespace()).doesNotContain(","); + assertThat(first.getText()).isEqualTo("apt-get"); + + // Second argument has only whitespace in prefix (comma is printed separately) + Docker.Literal second = execForm.getArguments().get(1); + assertThat(second.getPrefix().getWhitespace()).doesNotContain(","); + assertThat(second.getPrefix().getWhitespace()).isEqualTo(" "); + assertThat(second.getText()).isEqualTo("update"); + + // Third argument has only whitespace in prefix (comma is printed separately) + Docker.Literal third = execForm.getArguments().get(2); + assertThat(third.getPrefix().getWhitespace()).doesNotContain(","); + assertThat(third.getPrefix().getWhitespace()).isEqualTo(" "); + assertThat(third.getText()).isEqualTo("-y"); + }) + ) + ); + } + + @Test + void execFormWithSpacesAfterCommas() { + // Verify that spaces after commas are preserved in the prefix (without the comma) + rewriteRun( + docker( + """ + FROM ubuntu:20.04 + RUN ["apt-get", "update"] + """, + spec -> spec.afterRecipe(doc -> { + Docker.Run run = (Docker.Run) doc.getStages().getFirst().getInstructions().getLast(); + Docker.ExecForm execForm = (Docker.ExecForm) run.getCommand(); + assertThat(execForm.getArguments()).hasSize(2); + + // Second argument has two spaces in its prefix (comma is printed separately) + Docker.Literal second = execForm.getArguments().get(1); + assertThat(second.getPrefix().getWhitespace()).isEqualTo(" "); + }) + ) + ); + } + + @Test + void commentLineWithoutBacktick() { + rewriteRun( + docker(""" + FROM mcr.microsoft.com/windows/servercore:ltsc2022 + RUN powershell -Command " ` + $var = 'value'; ` + # Comment with no trailing backtick <-- this line breaks parsing + $next = 'value'; ` + ..." + """) + ); + } +} diff --git a/rewrite-docker/src/test/java/org/openrewrite/docker/tree/ShellTest.java b/rewrite-docker/src/test/java/org/openrewrite/docker/tree/ShellTest.java new file mode 100644 index 0000000000..ab900c2813 --- /dev/null +++ b/rewrite-docker/src/test/java/org/openrewrite/docker/tree/ShellTest.java @@ -0,0 +1,93 @@ +/* + * Copyright 2026 the original author or authors. + *

+ * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * https://www.apache.org/licenses/LICENSE-2.0 + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.openrewrite.docker.tree; + +import org.junit.jupiter.api.Test; +import org.openrewrite.test.RewriteTest; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.openrewrite.docker.Assertions.docker; + +class ShellTest implements RewriteTest { + + @Test + void shellInstruction() { + rewriteRun( + docker( + """ + FROM ubuntu:20.04 + SHELL ["/bin/bash", "-c"] + """, + spec -> spec.afterRecipe(doc -> { + Docker.Shell shell = (Docker.Shell) doc.getStages().getFirst().getInstructions().getLast(); + assertThat(shell.getArguments()).hasSize(2); + }) + ) + ); + } + + @Test + void shellWithSpacesInJsonArray() { + // SHELL with spaces inside JSON array + rewriteRun( + docker( + """ + FROM ubuntu:20.04 + SHELL [ "/bin/bash", "-c" ] + """ + ) + ); + } + + @Test + void shellWithMultipleSpacesBeforeBracket() { + // SHELL with multiple spaces between keyword and opening bracket + rewriteRun( + docker( + """ + FROM ubuntu:20.04 + SHELL ["/bin/bash", "-c"] + """ + ) + ); + } + + @Test + void shellWithTabBeforeBracket() { + // SHELL with tab between keyword and opening bracket + rewriteRun( + docker( + """ + FROM ubuntu:20.04 + SHELL\t["/bin/bash", "-c"] + """ + ) + ); + } + + @Test + void shellWithNoSpaceBeforeBracket() { + // SHELL with no space between keyword and opening bracket + rewriteRun( + docker( + """ + FROM ubuntu:20.04 + SHELL["/bin/bash", "-c"] + """ + ) + ); + } +} diff --git a/rewrite-docker/src/test/java/org/openrewrite/docker/tree/StopsignalTest.java b/rewrite-docker/src/test/java/org/openrewrite/docker/tree/StopsignalTest.java new file mode 100644 index 0000000000..67a146830f --- /dev/null +++ b/rewrite-docker/src/test/java/org/openrewrite/docker/tree/StopsignalTest.java @@ -0,0 +1,41 @@ +/* + * Copyright 2026 the original author or authors. + *

+ * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * https://www.apache.org/licenses/LICENSE-2.0 + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.openrewrite.docker.tree; + +import org.junit.jupiter.api.Test; +import org.openrewrite.test.RewriteTest; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.openrewrite.docker.Assertions.docker; + +class StopsignalTest implements RewriteTest { + + @Test + void stopsignalInstruction() { + rewriteRun( + docker( + """ + FROM ubuntu:20.04 + STOPSIGNAL SIGTERM + """, + spec -> spec.afterRecipe(doc -> { + Docker.Stopsignal stopsignal = (Docker.Stopsignal) doc.getStages().getFirst().getInstructions().getLast(); + assertThat(((Docker.Literal) stopsignal.getSignal().getContents().getFirst()).getText()).isEqualTo("SIGTERM"); + }) + ) + ); + } +} diff --git a/rewrite-docker/src/test/java/org/openrewrite/docker/tree/UserTest.java b/rewrite-docker/src/test/java/org/openrewrite/docker/tree/UserTest.java new file mode 100644 index 0000000000..e8e573f8c4 --- /dev/null +++ b/rewrite-docker/src/test/java/org/openrewrite/docker/tree/UserTest.java @@ -0,0 +1,59 @@ +/* + * Copyright 2026 the original author or authors. + *

+ * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * https://www.apache.org/licenses/LICENSE-2.0 + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.openrewrite.docker.tree; + +import org.junit.jupiter.api.Test; +import org.openrewrite.test.RewriteTest; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.openrewrite.docker.Assertions.docker; + +class UserTest implements RewriteTest { + + @Test + void userInstruction() { + rewriteRun( + docker( + """ + FROM ubuntu:20.04 + USER nobody + """, + spec -> spec.afterRecipe(doc -> { + Docker.User user = (Docker.User) doc.getStages().getFirst().getInstructions().getLast(); + assertThat(((Docker.Literal) user.getUser().getContents().getFirst()).getText()).isEqualTo("nobody"); + assertThat(user.getGroup()).isNull(); + }) + ) + ); + } + + @Test + void userWithGroup() { + rewriteRun( + docker( + """ + FROM ubuntu:20.04 + USER app:group + """, + spec -> spec.afterRecipe(doc -> { + Docker.User user = (Docker.User) doc.getStages().getFirst().getInstructions().getLast(); + assertThat(((Docker.Literal) user.getUser().getContents().getFirst()).getText()).isEqualTo("app"); + assertThat(((Docker.Literal) user.getGroup().getContents().getFirst()).getText()).isEqualTo("group"); + }) + ) + ); + } +} diff --git a/rewrite-docker/src/test/java/org/openrewrite/docker/tree/VolumeTest.java b/rewrite-docker/src/test/java/org/openrewrite/docker/tree/VolumeTest.java new file mode 100644 index 0000000000..31bb59e8d7 --- /dev/null +++ b/rewrite-docker/src/test/java/org/openrewrite/docker/tree/VolumeTest.java @@ -0,0 +1,140 @@ +/* + * Copyright 2026 the original author or authors. + *

+ * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * https://www.apache.org/licenses/LICENSE-2.0 + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.openrewrite.docker.tree; + +import org.junit.jupiter.api.Test; +import org.openrewrite.test.RewriteTest; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.openrewrite.docker.Assertions.docker; + +class VolumeTest implements RewriteTest { + + @Test + void volumeWithJsonArray() { + rewriteRun( + docker( + """ + FROM ubuntu:20.04 + VOLUME ["/data", "/logs"] + """, + spec -> spec.afterRecipe(doc -> { + Docker.Volume volume = (Docker.Volume) doc.getStages().getFirst().getInstructions().getLast(); + assertThat(volume.getValues()).hasSize(2); + assertThat(volume.isJsonForm()).isTrue(); + }) + ) + ); + } + + @Test + void volumeWithPathList() { + rewriteRun( + docker( + """ + FROM ubuntu:20.04 + VOLUME /data /logs + """, + spec -> spec.afterRecipe(doc -> { + Docker.Volume volume = (Docker.Volume) doc.getStages().getFirst().getInstructions().getLast(); + assertThat(volume.getValues()).hasSize(2); + assertThat(volume.isJsonForm()).isFalse(); + assertThat(((Docker.Literal) volume.getValues().get(0).getContents().getFirst()).getText()).isEqualTo("/data"); + assertThat(((Docker.Literal) volume.getValues().get(1).getContents().getFirst()).getText()).isEqualTo("/logs"); + }) + ) + ); + } + + @Test + void volumeJsonArrayWithSpaces() { + // VOLUME with spaces inside JSON array: VOLUME [ "/data" ] + rewriteRun( + docker( + """ + FROM ubuntu:20.04 + VOLUME [ "/data" ] + """ + ) + ); + } + + @Test + void volumeWithEnvironmentVariable() { + // VOLUME with environment variable reference + rewriteRun( + docker( + """ + FROM ubuntu:20.04 + ENV DATA_DIR=/data + VOLUME ${DATA_DIR} + """ + ) + ); + } + + @Test + void volumeWithMultipleSpacesBeforeBracket() { + // VOLUME with multiple spaces between keyword and opening bracket + rewriteRun( + docker( + """ + FROM ubuntu:20.04 + VOLUME ["/data"] + """ + ) + ); + } + + @Test + void volumeWithTabBeforeBracket() { + // VOLUME with tab between keyword and opening bracket + rewriteRun( + docker( + """ + FROM ubuntu:20.04 + VOLUME\t["/data"] + """ + ) + ); + } + + @Test + void volumeWithNoSpaceBeforeBracket() { + // VOLUME with no space between keyword and opening bracket + rewriteRun( + docker( + """ + FROM ubuntu:20.04 + VOLUME["/data"] + """ + ) + ); + } + + @Test + void volumeWithMultipleSpacesBeforePath() { + // VOLUME with multiple spaces between keyword and path (non-JSON form) + rewriteRun( + docker( + """ + FROM ubuntu:20.04 + VOLUME /data + """ + ) + ); + } +} diff --git a/rewrite-docker/src/test/java/org/openrewrite/docker/tree/WorkdirTest.java b/rewrite-docker/src/test/java/org/openrewrite/docker/tree/WorkdirTest.java new file mode 100644 index 0000000000..622b5577f4 --- /dev/null +++ b/rewrite-docker/src/test/java/org/openrewrite/docker/tree/WorkdirTest.java @@ -0,0 +1,41 @@ +/* + * Copyright 2026 the original author or authors. + *

+ * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * https://www.apache.org/licenses/LICENSE-2.0 + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.openrewrite.docker.tree; + +import org.junit.jupiter.api.Test; +import org.openrewrite.test.RewriteTest; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.openrewrite.docker.Assertions.docker; + +class WorkdirTest implements RewriteTest { + + @Test + void workdirInstruction() { + rewriteRun( + docker( + """ + FROM ubuntu:20.04 + WORKDIR /app + """, + spec -> spec.afterRecipe(doc -> { + Docker.Workdir workdir = (Docker.Workdir) doc.getStages().getFirst().getInstructions().getLast(); + assertThat(((Docker.Literal) workdir.getPath().getContents().getFirst()).getText()).isEqualTo("/app"); + }) + ) + ); + } +} diff --git a/settings.gradle.kts b/settings.gradle.kts index 774ad8f788..ad6e1440de 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -14,6 +14,7 @@ val allProjects = listOf( "rewrite-benchmarks", "rewrite-bom", "rewrite-core", + "rewrite-docker", "rewrite-gradle", "rewrite-gradle-tooling-model:model", "rewrite-gradle-tooling-model:plugin",