Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions ShellCheck.cabal
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ library
ShellCheck.Checks.Custom
ShellCheck.Checks.ShellSupport
ShellCheck.Data
ShellCheck.EditorConfig
ShellCheck.Fixer
ShellCheck.Formatter.Format
ShellCheck.Formatter.CheckStyle
Expand Down
31 changes: 31 additions & 0 deletions shellcheck.1.md
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,12 @@ not warn at all, as `ksh` supports decimals in arithmetic contexts.
read the list from standard input. This option is processed in addition to
any files specified on the command line.

**--file-name** *FILE*

: When checking standard input (`-`), use *FILE* as the filename to resolve
`.shellcheckrc` and EditorConfig configuration, instead of `-`. This has no
effect when checking regular files.


# FORMATS

Expand Down Expand Up @@ -334,6 +340,31 @@ Use `shellcheckrc` without the dot instead.
Note for Docker users: ShellCheck will only be able to look for files that
are mounted in the container, so `~/.shellcheckrc` will not be read.

# EDITORCONFIG

Unless `--norc` is used, ShellCheck will also look for a file `.editorconfig`
in the script's directory and each parent directory. Any section whose glob
pattern matches the checked file will have its `shellcheck.*` keys read as
directives, with the `shellcheck.` prefix stripped. This uses the same
`key=value` syntax as `.shellcheckrc`.

For example:

[*.{ebuild,eclass}]
shellcheck.shell=bash
shellcheck.disable=SC2034

[{PKGBUILD,APKBUILD}]
shellcheck.shell=bash
shellcheck.disable=SC2034

If no matching directives are found in any `.editorconfig` in the parent
directories, ShellCheck will look in the global default
`$XDG_CONFIG_HOME/editorconfig.ini` (usually `~/.config/editorconfig.ini`).

Directives from `.shellcheckrc`/`shellcheckrc` and from `.editorconfig` are
both applied, with `.shellcheckrc` taking precedence in case of conflicts.


# ENVIRONMENT VARIABLES

Expand Down
98 changes: 92 additions & 6 deletions shellcheck.hs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import qualified ShellCheck.Analyzer
import ShellCheck.Checker
import ShellCheck.Data
import ShellCheck.EditorConfig
import ShellCheck.Interface
import ShellCheck.Regex

Expand Down Expand Up @@ -77,7 +78,8 @@ data Options = Options {
sourcePaths :: [FilePath],
formatterOptions :: FormatterOptions,
minSeverity :: Severity,
rcfile :: Maybe FilePath
rcfile :: Maybe FilePath,
fileNameOverride :: Maybe FilePath
}

defaultOptions = Options {
Expand All @@ -88,7 +90,8 @@ defaultOptions = Options {
foColorOption = ColorAuto
},
minSeverity = StyleC,
rcfile = Nothing
rcfile = Nothing,
fileNameOverride = Nothing
}

usageHeader = "Usage: shellcheck [OPTIONS...] FILES..."
Expand All @@ -110,7 +113,7 @@ options = [
Option "" ["list-optional"]
(NoArg $ Flag "list-optional" "true") "List checks disabled by default",
Option "" ["norc"]
(NoArg $ Flag "norc" "true") "Don't look for .shellcheckrc files",
(NoArg $ Flag "norc" "true") "Don't look for .shellcheckrc and .editorconfig files",
Option "" ["rcfile"]
(ReqArg (Flag "rcfile") "RCFILE")
"Prefer the specified configuration file over searching for one",
Expand All @@ -137,7 +140,10 @@ options = [
(NoArg $ Flag "help" "true") "Show this usage summary and exit",
Option "" ["files-from"]
(ReqArg (Flag "files-from") "FILE")
"Read input files from FILE (one per line, or '-' for stdin)"
"Read input files from FILE (one per line, or '-' for stdin)",
Option "" ["file-name"]
(ReqArg (Flag "file-name") "FILE")
"Use FILE as the filename for parsing EditorConfig configuration when input is stdin"
]
getUsageInfo = usageInfo usageHeader options

Expand Down Expand Up @@ -420,6 +426,11 @@ parseOption flag options =
rcfile = Just str
}

Flag "file-name" str -> do
return options {
fileNameOverride = Just str
}

Flag "enable" value ->
let cs = checkSpec options in return options {
checkSpec = cs {
Expand Down Expand Up @@ -514,8 +525,26 @@ ioInterface options files = do
fallback path _ = return path


-- Returns the name and contents of .shellcheckrc for the given file
getConfig cache filename =
-- Returns the name and contents of .shellcheckrc for the given file,
-- merged with any shellcheck.* directives found in applicable
-- EditorConfig files.
getConfig cache filename = do
let configFilename =
if filename == "-"
then fromMaybe filename (fileNameOverride options)
else filename
rcResult <- getRcConfig cache configFilename
ecResult <- getEditorConfig configFilename
return $ mergeConfigs filename rcResult ecResult

mergeConfigs filename rcResult ecResult =
case (rcResult, ecResult) of
(Nothing, Nothing) -> Nothing
(Just (rcPath, rc), Nothing) -> Just (rcPath, rc)
(Nothing, Just (ecPath, ec)) -> Just (ecPath, ec)
(Just (rcPath, rc), Just (_, ec)) -> Just (rcPath, rc ++ "\n" ++ ec)

getRcConfig cache filename =
case rcfile options of
Just file -> do
-- We have a specified rcfile. Ignore normal rcfile resolution.
Expand All @@ -541,6 +570,63 @@ ioInterface options files = do
writeIORef cache (dir, result)
return result

-- Look for .editorconfig files in the target file's directory and
-- all its parents (as per the EditorConfig spec), plus the global
-- ${XDG_CONFIG_HOME}/editorconfig.ini default. shellcheck.* keys in
-- matching sections are turned into directives.
getEditorConfig filename = do
-- Resolve the directory (to find .editorconfig files) but keep
-- the leaf filename as-is so that globs match the symlink name
-- rather than the resolved target.
let name = takeFileName filename
dir <- normalize (takeDirectory filename)
let path = dir </> name
dirConfigs <- collectDirConfigs dir
globalConfig <- readGlobalEditorConfig
let allConfigs = dirConfigs ++ globalConfig
contributions = concatMap (directivesFor path) allConfigs
return $ case contributions of
[] -> Nothing
((sourceFile, _):_) -> Just (sourceFile, concatMap snd contributions)
where
-- For each EditorConfig file: either report an invalid 'root'
-- declaration at its line, or yield the matching shellcheck.*
-- directives. Invalid roots take priority over directives.
directivesFor path (file, contents) =
case invalidRootLines contents of
(badLine:_) ->
[(file, replicate (badLine - 1) '\n' ++ "invalid editorconfig value\n")]
[] ->
let relative = makeRelativeTo (takeDirectory file) path
result = parseEditorConfig contents relative
in if null result then [] else [(file, result)]

makeRelativeTo dir path =
case stripPrefix (addTrailingSlash dir) path of
Just rest -> rest
Nothing -> takeFileName path

addTrailingSlash dir
| null dir = dir
| last dir == '/' = dir
| otherwise = dir ++ "/"

collectDirConfigs dir = do
current <- readConfig (dir </> ".editorconfig")
let isRoot = maybe False (isEditorConfigRoot . snd) current
next = takeDirectory dir
rest <- if next /= dir && not isRoot
then collectDirConfigs next
else return []
return $ maybeToList current ++ rest

readGlobalEditorConfig = do
path <- (getXdgDirectory XdgConfig "editorconfig.ini")
`catch` ((const $ return "") :: IOException -> IO FilePath)
if null path
then return []
else maybeToList <$> readConfig path

findConfig paths =
case paths of
(file:rest) -> do
Expand Down
Loading
Loading