Documentation
¶
Overview ¶
Package shell provides shell script parsing utilities for Dockerfile linting.
Package shell provides shell script parsing utilities for Dockerfile linting.
Package shell provides shell script parsing utilities for Dockerfile linting.
Package shell provides shell script parsing utilities for Dockerfile linting.
Package shell provides shell script parsing utilities for Dockerfile linting. It wraps mvdan.cc/sh/v3/syntax to provide a simple API for extracting command names from shell scripts, similar to how hadolint uses ShellCheck.
Index ¶
- Variables
- func ApplySymbolicMode(symbolic string, baseMode uint16) uint16
- func Basename(p string) string
- func CanParsePowerShellScript(script string) bool
- func CommandNames(script string) []string
- func CommandNamesWithVariant(script string, variant Variant) []string
- func ContainsCommand(script, command string) bool
- func ContainsCommandWithVariant(script, command string, variant Variant) bool
- func ContinuationRune(variant Variant) rune
- func CountChainedCommands(script string, variant Variant) int
- func DockerCommandNames(cmdLine []string, prependShell bool, variant Variant) []string
- func DockerfileRunCommandStartCol(firstLine string) int
- func DownloadOutputFile(cmd *CommandInfo) string
- func DownloadURL(cmd *CommandInfo) string
- func DropQuotes(s string) string
- func ExtractChainSeparators(script string, variant Variant, commandCount int) []string
- func ExtractChainedCommands(script string, variant Variant) []string
- func ExtractCommandsBetweenCds(remaining string, variant Variant) string
- func FormatChainedScript(script string, variant Variant) string
- func FormatOctalMode(mode uint16) string
- func FormatStatement(stmt *syntax.Stmt, variant Variant) string
- func HasActionableGitSourceOpportunity(script string, variant Variant, workdir string) bool
- func HasCdAtStart(script string, variant Variant) bool
- func HasExitCommand(script string, variant Variant) bool
- func HasGitCloneRemote(script string, variant Variant) bool
- func HasPipes(script string, variant Variant) bool
- func HasStandaloneCd(script string, variant Variant) bool
- func IsArchiveFilename(name string) bool
- func IsArchiveURL(s string) bool
- func IsHeredocCandidate(script string, variant Variant, minCommands int) bool
- func IsOctalMode(s string) bool
- func IsPureFileCreation(script string, variant Variant) bool
- func IsSimpleScript(script string, variant Variant) bool
- func IsSymbolicMode(s string) bool
- func IsTarExtract(cmd *CommandInfo) bool
- func IsURL(s string) bool
- func IterateWrapperArgs(args []*syntax.Word, wrapperName string, callback func(WrapperArg) bool)
- func LooksLikeJSONExecForm(script string, variant Variant) bool
- func MergeChmodModes(existingMode, chmodRaw string) (string, bool)
- func NextShellToken(s string, start int) (string, int)
- func NormalizeShellExecutableName(exe string) string
- func ParseOctalMode(s string) uint16
- func PowerShellAssignment(script string) (string, string, bool)
- func ReconstructSourceText(lines []string, cmdStartCol int, escapeToken rune) string
- func ReconstructSourceTextForVariant(lines []string, cmdStartCol int, escapeToken rune, variant Variant) string
- func ScriptHasInlineHeredoc(script string, variant Variant) bool
- func SetsErrorFlag(cmd string, variant Variant) bool
- func ShellFromShebang(line string) (string, bool)
- func SkipDockerfileFlagValue(line string, offset int, stopAtLineContinuation bool) int
- func SkipShellTokenSpaces(s string, start int) int
- func SplitSimpleCommand(cmd string, variant Variant) ([]string, bool)
- func StripPackageVersion(name string) string
- func TarDestination(cmd *CommandInfo) string
- type ArgRange
- type CdCommand
- type ChainBoundary
- type ChainPosition
- type ChmodInfo
- type CmdScriptAnalysis
- type CommandInfo
- type CommandMatcher
- type CommandOccurrence
- type CommandSourceKind
- type ExplicitShellInvocation
- type FileCreationInfo
- type FileCreationOptions
- type GitSourceOpportunity
- type InstallCommand
- type PackageArg
- type PackageInstallInfo
- type PackageManager
- type Variant
- type WrapperArg
Constants ¶
This section is empty.
Variables ¶
var ArchiveExtensions = []string{
".tar.lzma",
".tar.bz2",
".tar.gz",
".tar.xz",
".tar.zst",
".tar.lz",
".tar.Z",
".lzma",
".tbz2",
".tzst",
".tar",
".tbz",
".tb2",
".tgz",
".tlz",
".tpz",
".txz",
".bz2",
".tZ",
".gz",
".lz",
".xz",
".Z",
}
ArchiveExtensions is the unified superset of archive file extensions recognized by both DL3010 (hadolint) and prefer-add-unpack (tally). Sorted longest-first so suffix matching is greedy (e.g. ".tar.gz" is checked before ".gz").
var DownloadCommands = []string{"curl", "wget", invokeWebRequestCommand, iwrCommand}
DownloadCommands lists commands that download remote files.
var ExtractionCommands = []string{
"bunzip2",
"gzcat",
"gunzip",
"uncompress",
"unlzma",
"unxz",
"unzip",
"zcat",
"zgz",
}
ExtractionCommands lists commands that extract archive files (excluding tar, which needs separate flag checking via IsTarExtract).
Functions ¶
func ApplySymbolicMode ¶ added in v0.29.0
ApplySymbolicMode converts a symbolic chmod mode to octal, given a base mode. Returns 0 if the mode cannot be converted. Supports: [ugoa]*[+-=][rwx]+ (not X, s, t which are complex/rare).
func Basename ¶
Basename extracts the filename from a path, stripping quotes and handling both Unix and Windows separators.
func CanParsePowerShellScript ¶ added in v0.32.0
CanParsePowerShellScript reports whether the PowerShell tree-sitter grammar can parse the script without errors.
func CommandNames ¶
CommandNames extracts all command names from a shell script. Uses VariantBash by default. Use CommandNamesWithVariant for other shells.
func CommandNamesWithVariant ¶
CommandNamesWithVariant extracts all command names from a shell script using the specified shell variant for parsing.
It parses the script and walks the AST to find all CallExpr nodes, returning the first word of each (the command name). It also handles command wrappers (env, nice, xargs, etc.) and shell wrappers (sh -c, bash -c).
This matches hadolint's behavior using ShellCheck.findCommandNames.
func ContainsCommand ¶
ContainsCommand checks if a shell script contains a specific command. Uses VariantBash by default.
func ContainsCommandWithVariant ¶
ContainsCommandWithVariant checks if a shell script contains a specific command using the specified shell variant for parsing.
func ContinuationRune ¶ added in v0.32.0
ContinuationRune returns the native line-continuation character for a shell variant.
func CountChainedCommands ¶
CountChainedCommands counts the number of commands in && chains within a shell script. Pipelines (|) count as a single logical command. Top-level statements separated by semicolons or newlines are counted individually.
func DockerCommandNames ¶ added in v0.31.0
DockerCommandNames extracts command names from CMD/ENTRYPOINT arguments.
Shell form delegates to CommandNamesWithVariant so wrappers like "exec" or "env" are handled consistently with RUN parsing. Exec form returns the base executable name from argv[0].
func DockerfileRunCommandStartCol ¶ added in v0.28.1
DockerfileRunCommandStartCol returns the byte offset where the shell command begins in the first line of a RUN instruction, after RUN and any leading flags.
func DownloadOutputFile ¶
func DownloadOutputFile(cmd *CommandInfo) string
DownloadOutputFile extracts the output filename from a curl or wget CommandInfo. For curl: -o <file>, -o<file>, --output <file>, --output=<file> For wget: -O <file>, -O<file>, --output-document <file>, --output-document=<file> Returns "" if no output file is specified or if output is stdout ("-").
func DownloadURL ¶
func DownloadURL(cmd *CommandInfo) string
DownloadURL extracts the first URL argument (http/https/ftp) from a download CommandInfo. Returns "" if no URL is found.
func DropQuotes ¶
DropQuotes removes surrounding single or double quotes from a string.
func ExtractChainSeparators ¶ added in v0.10.0
ExtractChainSeparators returns the raw separator text between commands in an && chain (for example " && " or " && \\\n "). The result length is commandCount-1 on success; otherwise nil.
func ExtractChainedCommands ¶
ExtractChainedCommands extracts individual command strings from && chains. Each command is formatted cleanly using the shell printer. Returns nil if parsing fails or for non-POSIX shells.
func ExtractCommandsBetweenCds ¶
ExtractCommandsBetweenCds parses the remaining commands after a cd and extracts commands that come before the next cd. This properly handles quoted paths. For "make && cd /tmp && build", if we're looking for commands before "cd /tmp", this returns "make".
func FormatChainedScript ¶ added in v0.11.0
FormatChainedScript formats a shell script so that each top-level &&/|| chain operator starts on its own line. Uses the mvdan.cc/sh/v3 printer with BinaryNextLine for correct shell formatting. Returns the original script text (trimmed) if parsing fails or there are no chain operators.
The output uses tab indentation for continuation lines (Indent(0) = tabs).
func FormatOctalMode ¶
FormatOctalMode formats a chmod mode as a 4-digit octal string. E.g., 0o755 -> "0755", 0o644 -> "0644" Returns empty string for 0 (no mode).
func FormatStatement ¶
FormatStatement formats a single statement using syntax.Printer. Returns a clean, single-line representation of the command.
func HasActionableGitSourceOpportunity ¶ added in v0.31.0
HasActionableGitSourceOpportunity reports whether a script has a git-clone flow that prefer-add-git can extract into ADD <git source>.
func HasCdAtStart ¶
HasCdAtStart returns true if the script has cd at the beginning of a command chain.
func HasExitCommand ¶
HasExitCommand checks if a script contains exit commands that would change control flow if merged with other commands.
func HasGitCloneRemote ¶ added in v0.31.0
HasGitCloneRemote reports whether a script contains a git clone of a remote repository.
func HasPipes ¶
HasPipes checks if a shell script contains any pipe operators (| or |&). Returns false for non-POSIX shells or unparseable scripts.
func HasStandaloneCd ¶
HasStandaloneCd returns true if the script contains a standalone cd command (one that isn't chained with other commands).
func IsArchiveFilename ¶
IsArchiveFilename checks if a filename has a recognized archive extension. Extensions are case-sensitive (e.g. .Z and .tZ use uppercase Z for Unix compress format).
func IsArchiveURL ¶
IsArchiveURL checks if a URL string points to an archive file. Strips query/fragment before checking extension. Requires http/https/ftp scheme.
func IsHeredocCandidate ¶
IsHeredocCandidate checks if a shell script would be a good candidate for heredoc conversion by the prefer-run-heredoc rule. This is used by other rules (like DL3003) to avoid generating fixes that would interfere with heredoc conversion.
A script is a heredoc candidate if:
- It uses a POSIX shell
- It has at least minCommands commands (from && chains or separate statements)
- It's a simple script (no complex control flow like if/for/while)
This function parses the script once and reuses the AST for all checks.
func IsOctalMode ¶ added in v0.29.0
IsOctalMode checks if a string is a valid octal chmod mode.
func IsPureFileCreation ¶
IsPureFileCreation checks if a shell script is PURELY for creating files. Returns true only if every command in the script is for file creation (echo/cat/printf > file) or chmod on the created file. Returns false if there are any other commands mixed in. This is used by prefer-run-heredoc to yield to prefer-copy-heredoc.
func IsSimpleScript ¶
IsSimpleScript checks if a script contains only simple commands that can be safely merged into a heredoc. Returns false for scripts with compound commands (if, for, while, case), control flow (return, break, continue, exec), functions, or subshells. `exit` is allowed so that common guard patterns like `cd dir || exit` (often suggested by ShellCheck) don't block heredoc conversion.
func IsSymbolicMode ¶ added in v0.29.0
IsSymbolicMode checks if a string is a symbolic chmod mode.
func IsTarExtract ¶
func IsTarExtract(cmd *CommandInfo) bool
IsTarExtract checks if a tar CommandInfo has extraction flags (-x, --extract, --get).
func IterateWrapperArgs ¶
func IterateWrapperArgs(args []*syntax.Word, wrapperName string, callback func(WrapperArg) bool)
IterateWrapperArgs iterates through wrapper command arguments, skipping flags and their values, and calls the callback for each potential command argument found. This handles the common pattern of finding commands within sudo, env, etc.
The callback should return true to break iteration, false to continue looking for nested wrappers.
func LooksLikeJSONExecForm ¶ added in v0.28.0
LooksLikeJSONExecForm reports whether a shell script fragment appears to be a Docker JSON exec-form attempt that was not valid JSON. It uses the active shell variant's parser to distinguish valid shell syntax from likely exec-form mistakes.
For POSIX shells (bash, sh, etc.), the script is parsed with mvdan.cc/sh and the AST is inspected: a top-level CallExpr whose first word starts with [ or last word ends with ] signals a likely JSON attempt, while proper test constructs (TestClause, BinaryCmd) are recognized as valid shell.
For PowerShell, the tree-sitter grammar is used: if the script parses without errors, it is valid PowerShell (e.g., [ClassName]::Method) and not JSON.
func MergeChmodModes ¶ added in v0.29.0
MergeChmodModes computes the resulting mode when a chmod operation is applied on top of an existing mode string (e.g. from COPY --chmod).
If chmodRaw is an absolute octal mode (e.g. "755"), it overrides existingMode entirely. If chmodRaw is symbolic (e.g. "+x"), it is applied on top of existingMode. Returns the resulting mode string and true on success, or ("", false) if parsing fails.
func NextShellToken ¶ added in v0.27.2
NextShellToken returns the next shell-like token starting at or after start.
func NormalizeShellExecutableName ¶ added in v0.27.2
NormalizeShellExecutableName canonicalizes a shell executable path/name to its lowercase basename without a trailing .exe suffix.
func ParseOctalMode ¶
ParseOctalMode parses an octal mode string (e.g., "755", "0755") to uint16. Returns 0 for invalid input.
func PowerShellAssignment ¶ added in v0.32.0
PowerShellAssignment returns the variable name and right-hand value for a simple top-level PowerShell assignment expression like "$ErrorActionPreference = 'Stop'".
func ReconstructSourceText ¶ added in v0.11.0
ReconstructSourceText reconstructs the shell command source text from Dockerfile source lines. Backslash-newline continuations are kept intact because the shell parser (mvdan.cc/sh) handles them natively. This preserves line/col positions for mapping back to the Dockerfile.
cmdStartCol is the byte offset in the first line where the command starts (after RUN + flags). Continuation lines are included in full. ReconstructSourceText joins Dockerfile instruction lines into a single string for the shell parser, respecting the Dockerfile escape token.
When the escape token is not backslash (e.g. backtick for Windows Dockerfiles), trailing escape characters on continuation lines are replaced with backslash so the bash shell parser handles line continuations correctly.
func ReconstructSourceTextForVariant ¶ added in v0.32.0
func ReconstructSourceTextForVariant( lines []string, cmdStartCol int, escapeToken rune, variant Variant, ) string
ReconstructSourceTextForVariant reconstructs Dockerfile shell source for the requested shell variant, rewriting Dockerfile continuation markers to the shell's native continuation token when needed.
func ScriptHasInlineHeredoc ¶ added in v0.11.0
ScriptHasInlineHeredoc checks whether a shell script contains inline heredocs (e.g., cat <<EOF ... EOF && other_cmd). Such scripts should not have their chain boundaries reformatted because the heredoc body positions would break.
func SetsErrorFlag ¶
SetsErrorFlag checks if a command is a "set" builtin that enables the -e flag. Uses shell AST to properly detect any flag combination containing 'e' (e.g., "set -e", "set -ex", "set -euo pipefail").
func ShellFromShebang ¶ added in v0.19.0
ShellFromShebang extracts the shell name from a shebang line. It delegates to fileutil.Shebang for the common cases (sh, bash, mksh, bats, zsh) and adds ksh support for Dockerfile compatibility.
Returns the normalized shell name (e.g., "bash", "sh", "ksh") and true if a known shell shebang was found. The returned name can be passed directly to VariantFromShell.
func SkipDockerfileFlagValue ¶ added in v0.28.1
SkipDockerfileFlagValue advances past a Dockerfile flag token starting at the provided offset. When stopAtLineContinuation is true, a trailing backslash is treated as the end of the token instead of part of the token.
func SkipShellTokenSpaces ¶ added in v0.27.2
SkipShellTokenSpaces advances over whitespace in shell-like command lines.
func SplitSimpleCommand ¶
SplitSimpleCommand parses a shell command string and returns its argv words.
This is intentionally conservative: it only succeeds for a single simple command without redirections, pipelines, boolean operators, variable expansions, command substitutions, or other shell-specific constructs.
This is useful for suggesting "exec form" JSON arrays for Dockerfile instructions like CMD/ENTRYPOINT when the shell form is trivially tokenizable.
func StripPackageVersion ¶ added in v0.35.2
StripPackageVersion returns the package name with any trailing version specifier removed. Handles the forms used by the package-manager syntaxes tally understands:
- apt/apk/dnf/yum/zypper: "name=1.2"
- pip: "name==1.2"
- npm/yarn/pnpm/bun: "name@1.2" — a leading "@" is a scope marker (e.g. "@types/node"), so the version separator is the first "@" past index 0, not the first "@" overall
- Debian arch qualifier / rpm epoch: "libfoo:amd64", "foo:1-2"
The caller is expected to pass an already-normalized (unquoted) token.
func TarDestination ¶
func TarDestination(cmd *CommandInfo) string
TarDestination extracts the target directory from a tar CommandInfo. Checks -C <dir>, --directory=<dir>, --directory <dir>. Returns "" if none found.
Types ¶
type ArgRange ¶ added in v0.35.2
ArgRange is the script-relative source range of a single argument token. Columns are 0-based byte offsets within Line; EndCol is exclusive.
type CdCommand ¶
type CdCommand struct {
// TargetDir is the directory argument passed to cd.
TargetDir string
// IsStandalone is true if cd is the only command (not chained with && or ;).
IsStandalone bool
// IsAtStart is true if cd is at the beginning of a command chain.
// e.g., "cd /foo && make" has cd at start, "make && cd /foo" does not.
IsAtStart bool
// PrecedingCommands contains the commands before cd if it's not at the start.
// e.g., for "mkdir /tmp && cd /tmp && make", this would be "mkdir /tmp".
PrecedingCommands string
// RemainingCommands contains the commands after "cd /foo &&" if IsAtStart is true.
// Empty if IsStandalone is true or cd is not at start.
RemainingCommands string
// StartCol is the 0-based column where cd starts.
StartCol int
// Line is the 0-based line number.
Line int
}
CdCommand represents a cd command found in a shell script.
func FindCdCommands ¶
FindCdCommands finds all cd commands in a shell script and analyzes their context.
type ChainBoundary ¶ added in v0.11.0
type ChainBoundary struct {
// LeftEndLine is the 1-based line (in the parsed text) where the left command ends.
LeftEndLine int
// LeftEndCol is the 1-based column where the left command ends.
LeftEndCol int
// OpLine is the 1-based line where the operator (&&/||) starts.
OpLine int
// OpCol is the 1-based column where the operator (&&/||) starts.
OpCol int
// RightStartLine is the 1-based line where the right command starts.
RightStartLine int
// RightStartCol is the 1-based column where the right command starts.
RightStartCol int
// Op is the operator text ("&&" or "||").
Op string
// SameLine is true when left end and right start are on the same source line.
SameLine bool
}
ChainBoundary represents the position of a chain operator (&&/||) between two commands in a shell script's source text.
func CollectChainBoundaries ¶ added in v0.11.0
func CollectChainBoundaries(scriptText string, variant Variant) ([]ChainBoundary, int)
CollectChainBoundaries parses a shell script and returns all top-level chain boundaries (&& and ||) along with the maximum per-chain command count. The per-chain count reflects the longest &&/|| chain in any single statement, not the sum across semicolon-separated statements. This is the correct value for comparing against a minCommands threshold.
The script text should include backslash continuations exactly as they appear in the Dockerfile source so that line/col positions map correctly.
Returns nil, 0 if parsing fails or for non-POSIX shells.
type ChainPosition ¶
type ChainPosition struct {
// IsStandalone is true if this is the only command (not chained).
IsStandalone bool
// HasOtherStatements is true when the script contains multiple top-level
// statements separated by semicolons or newlines. In this case,
// PrecedingCommands and RemainingCommands only cover the chain within
// the matched statement and do NOT include commands from other statements.
// Callers building replacement text for the entire script must not use
// this position alone, as it would silently drop sibling statements.
HasOtherStatements bool
// PrecedingCommands contains the commands before this one in the chain.
// Empty when the command is at the start or standalone.
PrecedingCommands string
// RemainingCommands contains the commands after this one in the chain.
// Empty when the command is at the end or standalone.
RemainingCommands string
}
ChainPosition describes a command's position within a && chain.
func FindCommandInChain ¶
func FindCommandInChain(script string, variant Variant, match CommandMatcher) *ChainPosition
FindCommandInChain locates the first command matching the predicate in a shell script and returns its chain context (preceding/remaining commands). Returns nil if no matching command is found or the script fails to parse.
type ChmodInfo ¶
type ChmodInfo struct {
// Mode is the octal mode (e.g., 0o755, 0o644, 0o4755).
Mode uint16
// RawMode is the original mode string as written (e.g., "+x", "755", "0755", "u+rwx").
// Useful for preserving notation in fixes (COPY --chmod supports both octal and symbolic).
RawMode string
// Target is the file path being chmod'd.
Target string
}
ChmodInfo describes a standalone chmod command.
func DetectStandaloneChmod ¶
DetectStandaloneChmod checks if a shell script is a standalone chmod command. Returns nil if it's not a pure chmod or if the chmod cannot be converted (e.g., symbolic mode, recursive chmod, multiple commands).
type CmdScriptAnalysis ¶ added in v0.27.2
type CmdScriptAnalysis struct {
Commands []CommandInfo
HasConditionals bool
HasExitCommand bool
HasPipes bool
HasRedirections bool
HasControlFlow bool
HasVariableReferences bool
// contains filtered or unexported fields
}
CmdScriptAnalysis captures the cmd.exe syntax traits that matter for fix safety when rewriting a stage to PowerShell.
func AnalyzeCmdScript ¶ added in v0.27.2
func AnalyzeCmdScript(script string) *CmdScriptAnalysis
AnalyzeCmdScript parses a cmd.exe script and returns the command list plus shell-feature signals used by Windows-safe autofixes.
func (*CmdScriptAnalysis) HasBatchOnlySyntax ¶ added in v0.27.2
func (a *CmdScriptAnalysis) HasBatchOnlySyntax() bool
HasBatchOnlySyntax reports whether the script uses cmd.exe shell semantics that should block generic rewrites to PowerShell.
type CommandInfo ¶
type CommandInfo struct {
// SourceKind describes how this command was discovered in the script.
// Only Direct commands have a source range that can be rewritten safely.
SourceKind CommandSourceKind
// Variant is the shell variant used to parse this command.
Variant Variant
// Name is the base command name (e.g., "apt-get", "yum").
// For Windows shells the name is normalized lowercase without a trailing ".exe";
// callers that need to distinguish an explicit .exe invocation from a shell alias
// (e.g., PowerShell's curl/wget aliases for Invoke-WebRequest) should use HasExeSuffix.
Name string
// HasExeSuffix is true when the source invocation ended with a ".exe" suffix.
// This is preserved across normalization and is used on PowerShell to distinguish
// the GNU binary (curl.exe) from the built-in alias (curl -> Invoke-WebRequest).
HasExeSuffix bool
// Subcommand is the first non-flag argument (e.g., "install" in "apt-get install").
Subcommand string
// Args contains all arguments including flags.
Args []string
// ArgLiteral reports whether the corresponding Args entry came from a shell
// word with no variable expansion, command substitution, or other dynamic
// evaluation. The slice is aligned with Args.
ArgLiteral []bool
// ArgRanges reports the script-relative source range for each arg. Aligned
// with Args; empty when the underlying parser didn't preserve positions.
// Positions are 0-based (line, column byte offset).
ArgRanges []ArgRange
// Position information for the command name.
Line int // 0-based line within the script
StartCol int // 0-based column where command starts
EndCol int // 0-based column where command name ends
// Position information for the full command expression.
// Only valid when HasCommandRange is true.
CommandEndLine int // 0-based line within the script where the command ends
CommandEndCol int // 0-based exclusive column where the command ends
HasCommandRange bool
// Position information for the subcommand (if present).
// These are only set when Subcommand is non-empty.
SubcommandLine int // 0-based line within the script
SubcommandStartCol int // 0-based column where subcommand starts
SubcommandEndCol int // 0-based column where subcommand ends
}
CommandInfo represents a parsed command with its arguments and flags.
func FindCommands ¶
func FindCommands(script string, variant Variant, names ...string) []CommandInfo
FindCommands extracts all commands matching the given name(s) from a shell script. It returns detailed CommandInfo for each matching command.
func (*CommandInfo) CountFlag ¶
func (c *CommandInfo) CountFlag(flag string) int
CountFlag counts how many times a flag appears in the command. Useful for checking flags like -q -q (equivalent to -qq).
func (*CommandInfo) GetArgValue ¶
func (c *CommandInfo) GetArgValue(flag string) string
GetArgValue returns the value following a flag (e.g., "-q=2" returns "2"). Returns empty string if not found or no value.
func (*CommandInfo) HasAnyArg ¶
func (c *CommandInfo) HasAnyArg(args ...string) bool
HasAnyArg checks if any of the specified arguments are present as the subcommand.
func (*CommandInfo) HasAnyFlag ¶
func (c *CommandInfo) HasAnyFlag(flags ...string) bool
HasAnyFlag checks if the command has any of the specified flags.
func (*CommandInfo) HasFlag ¶
func (c *CommandInfo) HasFlag(flag string) bool
HasFlag checks if the command has a specific flag. Handles both short flags (-y) and long flags (--yes). For short flags, also checks combined flags (e.g., -yq contains -y).
type CommandMatcher ¶
CommandMatcher is a predicate that decides whether a shell call expression is the command to locate. name is the base command name (path stripped), args are all arguments (flags and positional).
type CommandOccurrence ¶
type CommandOccurrence struct {
// Name is the command name (e.g., "apt", "sudo").
Name string
// Subcommand is the first argument if it looks like a subcommand (e.g., "install" in "apt install").
// Empty if the first argument looks like a flag or there are no arguments.
Subcommand string
// StartCol is the 0-based column offset where the command starts.
StartCol int
// EndCol is the 0-based column offset where the command name ends (exclusive).
EndCol int
// Line is the 0-based line number within the script where the command appears.
Line int
}
CommandOccurrence represents a command with its exact position in the script.
func FindAllCommandOccurrences ¶
func FindAllCommandOccurrences(script, command string, variant Variant) []CommandOccurrence
FindAllCommandOccurrences finds all occurrences of a specific command.
func FindCommandOccurrence ¶
func FindCommandOccurrence(script, command string, variant Variant) *CommandOccurrence
FindCommandOccurrence finds the first occurrence of a specific command. Returns nil if the command is not found.
func FindCommandOccurrences ¶
func FindCommandOccurrences(script string, variant Variant) []CommandOccurrence
FindCommandOccurrences extracts all command positions from a shell script. It returns occurrences with precise byte offsets for each command found.
type CommandSourceKind ¶ added in v0.35.0
type CommandSourceKind string
CommandSourceKind describes how a command was discovered in the shell AST.
const ( // CommandSourceKindDirect is a normal call expression in the current script. CommandSourceKindDirect CommandSourceKind = "direct" // CommandSourceKindWrapped is a command reached through a wrapper like env/nice/timeout. CommandSourceKindWrapped CommandSourceKind = "wrapped" // CommandSourceKindNestedShell is a command parsed from nested shell code like sh -c "cmd". CommandSourceKindNestedShell CommandSourceKind = "nested-shell" )
type ExplicitShellInvocation ¶ added in v0.27.2
ExplicitShellInvocation describes a shell wrapper explicitly invoked at the start of a shell-form instruction body, e.g. "pwsh -Command ..." or "cmd /c ...".
func ParseExplicitShellInvocation ¶ added in v0.27.2
func ParseExplicitShellInvocation(script string) (ExplicitShellInvocation, bool)
ParseExplicitShellInvocation detects a leading shell wrapper invocation and returns the wrapper shell plus the payload passed to that shell.
type FileCreationInfo ¶
type FileCreationInfo struct {
// TargetPath is the absolute path to the target file after any caller-supplied
// path resolution has been applied.
TargetPath string
// ResolvedHomePath is true when the original shell target used home expansion
// (for example "~/.bashrc") and the caller resolved it to an absolute path.
// Converting such patterns to COPY usually requires an unsafe fix because COPY
// does not support "~" directly.
ResolvedHomePath bool
// Content is the literal content to write.
Content string
// ChmodMode is the octal chmod mode (e.g., 0o755, 0o644), or 0 if no chmod.
ChmodMode uint16
// RawChmodMode is the original chmod mode string (e.g., "+x", "755", "0755").
// Preserved so fixes can use the notation the user wrote.
// Empty when ChmodMode is 0.
RawChmodMode string
// IsAppend is true if ALL writes in the chain use >> (append) mode.
// If true, converting to COPY would lose existing file content.
// A later > (overwrite) clears this flag since content no longer depends on existing data.
IsAppend bool
// HasUnsafeVariables is true if the script uses variables that cannot be
// converted to COPY heredoc (e.g., shell variables, command substitution).
HasUnsafeVariables bool
// PrecedingCommands contains commands before the file creation (for mixed scripts).
// Empty if file creation is at the start or script is pure file creation.
PrecedingCommands string
// RemainingCommands contains commands after the file creation (for mixed scripts).
// Empty if file creation is at the end or script is pure file creation.
RemainingCommands string
}
FileCreationInfo describes a detected file creation pattern in a shell script. This is used to coordinate between prefer-copy-heredoc and prefer-run-heredoc rules.
func DetectFileCreation ¶
func DetectFileCreation(script string, variant Variant, knownVars func(name string) bool) *FileCreationInfo
DetectFileCreation analyzes a shell script for file creation patterns. Returns nil if the script is not primarily a file creation operation.
Detected patterns:
- echo "content" > /path/to/file
- echo "content" >> /path/to/file (append)
- cat <<EOF > /path/to/file ... EOF
- printf "content" > /path/to/file (limited support)
- tee /path/to/file (with heredoc stdin)
Also detects chmod chaining: echo "x" > /file && chmod 0755 /file
The knownVars function is called to check if a variable is a known ARG/ENV. If nil, all variables are considered unsafe.
func DetectFileCreationWithOptions ¶ added in v0.31.0
func DetectFileCreationWithOptions( script string, variant Variant, knownVars func(name string) bool, options FileCreationOptions, ) *FileCreationInfo
DetectFileCreationWithOptions analyzes a shell script for file creation patterns with optional target-path rewriting and shell-specific echo semantics.
type FileCreationOptions ¶ added in v0.31.0
type FileCreationOptions struct {
// ResolveTargetPath lets callers rewrite literal targets like "~/.bashrc"
// to an absolute in-image path before matching.
ResolveTargetPath func(rawTarget string) (resolvedPath string, resolvedHomePath bool, ok bool)
// InterpretPlainEchoEscapes enables backslash-escape processing for plain
// echo output (for example \n -> newline) on shells where that behavior is
// part of the effective runtime semantics.
InterpretPlainEchoEscapes bool
}
FileCreationOptions controls optional behavior for file creation detection.
type GitSourceOpportunity ¶ added in v0.31.0
type GitSourceOpportunity struct {
AddSource string
AddDestination string
AddChecksum string
RepoPath string
PrecedingCommands string
RemainingCommands string
EnteredRepo bool
CheckoutRef string
CloneRef string
UsesSubmodules bool
KeepGitDir bool
NormalizedRemote string
}
GitSourceOpportunity describes a git-clone flow that can be replaced by ADD.
func FirstGitSourceOpportunity ¶ added in v0.31.0
func FirstGitSourceOpportunity(script string, variant Variant, workdir string) (*GitSourceOpportunity, bool)
FirstGitSourceOpportunity returns the first git-clone flow that can be safely extracted into an ADD git source. The returned fix plan is intentionally conservative: it only rewrites simple single-statement && chains.
type InstallCommand ¶ added in v0.20.0
type InstallCommand struct {
Manager string // e.g., "apt-get", "npm", "pip"
Subcommand string // e.g., "install", "add", "require"
Packages []PackageArg // non-flag args after subcommand, with positions
}
InstallCommand represents a detected package install command with per-argument positions.
func FindInstallPackages ¶ added in v0.20.0
func FindInstallPackages(script string, variant Variant) []InstallCommand
FindInstallPackages parses a shell script and extracts install commands with per-argument position information. Positions are 0-based line and column offsets within the source text.
Dispatches to a variant-aware path: shells with tree-sitter grammars (PowerShell, cmd) are handled via FindCommands so their native tokenization and line-continuation rules are honored; POSIX variants go through the mvdan.cc/sh AST walker for full flag/wrapper fidelity.
type PackageArg ¶ added in v0.20.0
type PackageArg struct {
// Value is the raw source token text (including any quotes), used for
// round-trip safe edits. The edit span (StartCol..EndCol) covers exactly
// these bytes.
Value string
// Normalized is the unquoted/decoded text used for sorting and comparison.
Normalized string
Line int // 0-based line within the source text
StartCol int // 0-based start column (byte offset)
EndCol int // 0-based end column (byte offset, exclusive)
IsVar bool // true if the argument contains a variable reference ($)
}
PackageArg represents a single package argument with its position in the source text.
type PackageInstallInfo ¶
type PackageInstallInfo struct {
Manager PackageManager
Packages []string
}
PackageInstallInfo represents a detected package installation.
func ExtractPackageInstalls ¶
func ExtractPackageInstalls(script string, variant Variant) []PackageInstallInfo
ExtractPackageInstalls parses a shell script and extracts package installations.
type PackageManager ¶
type PackageManager string
PackageManager identifies a system package manager.
const ( PackageManagerApt PackageManager = "apt" PackageManagerApk PackageManager = "apk" PackageManagerYum PackageManager = "yum" PackageManagerDnf PackageManager = "dnf" PackageManagerZypper PackageManager = "zypper" PackageManagerPacman PackageManager = "pacman" PackageManagerEmerge PackageManager = "emerge" PackageManagerUnknown PackageManager = "" )
type Variant ¶
type Variant int
Variant represents a shell variant for parsing and rule gating. It is a bitset so that capability sets can be defined and tested with a single bitwise AND (mirrors mvdan.cc/sh's LangVariant design).
const ( // VariantBash is the GNU Bash shell (default for Docker Linux containers). VariantBash Variant = 1 << iota // VariantPOSIX is the POSIX-compliant shell (sh, dash, ash). VariantPOSIX // VariantMksh is the MirBSD Korn Shell. VariantMksh // VariantZsh is the Z shell. VariantZsh // VariantPowerShell is PowerShell (cross-platform: powershell on Windows, pwsh on Linux/macOS). VariantPowerShell // VariantCmd is the Windows cmd.exe command interpreter. VariantCmd // VariantUnknown is an unrecognized shell. Treated conservatively: no parser support, // not ShellCheck-compatible, not PowerShell. VariantUnknown Variant = 0 )
func VariantFromScriptPath ¶ added in v0.31.0
VariantFromScriptPath returns the appropriate Variant for parsing a script based on its file extension. Defaults to VariantBash for unknown extensions.
func VariantFromShell ¶
VariantFromShell returns the appropriate Variant for a shell name. Common shell mappings:
- bash -> VariantBash
- sh, dash, ash -> VariantPOSIX
- mksh, ksh -> VariantMksh
- zsh -> VariantZsh
- powershell, pwsh -> VariantPowerShell
- cmd -> VariantCmd
- unknown -> VariantUnknown
func VariantFromShellCmd ¶
VariantFromShellCmd returns the appropriate Variant from a SHELL command array. The first element is typically the shell path (e.g., ["/bin/bash", "-c"]).
func (Variant) HasParser ¶ added in v0.24.0
HasParser returns true for shells with any parser backend wired into Tally. Use this to guard generic syntax-tree features such as command extraction.
func (Variant) IsPowerShell ¶ added in v0.19.0
IsPowerShell returns true for PowerShell variants (powershell, pwsh). Use this to gate PowerShell-specific lint rules (tally/powershell/*).
func (Variant) IsShellCheckCompatible ¶ added in v0.19.0
IsShellCheckCompatible returns true for shells that ShellCheck can analyze. Use this to guard ShellCheck WASM invocation.
func (Variant) SupportsHeredoc ¶ added in v0.19.0
SupportsHeredoc returns true for shells compatible with BuildKit RUN heredoc syntax (RUN <<EOF). Use this to guard heredoc suggestions and fixes.
func (Variant) SupportsPOSIXShellAST ¶ added in v0.24.0
SupportsPOSIXShellAST returns true for shells represented by mvdan.cc/sh. Use this to guard helpers that depend on POSIX shell syntax/semantics.
type WrapperArg ¶
type WrapperArg struct {
// Arg is the syntax.Word representing this argument
Arg *syntax.Word
// Index is the position in the args slice
Index int
// Name is the base name of the command (path.Base applied)
Name string
// RemainingArgs are the args after this command
RemainingArgs []*syntax.Word
}
WrapperArg represents a potential command argument found within wrapper arguments.