2020import qualified ShellCheck.Analyzer
2121import ShellCheck.Checker
2222import ShellCheck.Data
23+ import ShellCheck.EditorConfig
2324import ShellCheck.Interface
2425import ShellCheck.Regex
2526
@@ -110,7 +111,7 @@ options = [
110111 Option " " [" list-optional" ]
111112 (NoArg $ Flag " list-optional" " true" ) " List checks disabled by default" ,
112113 Option " " [" norc" ]
113- (NoArg $ Flag " norc" " true" ) " Don't look for .shellcheckrc files" ,
114+ (NoArg $ Flag " norc" " true" ) " Don't look for .shellcheckrc and .editorconfig files" ,
114115 Option " " [" rcfile" ]
115116 (ReqArg (Flag " rcfile" ) " RCFILE" )
116117 " Prefer the specified configuration file over searching for one" ,
@@ -514,8 +515,22 @@ ioInterface options files = do
514515 fallback path _ = return path
515516
516517
517- -- Returns the name and contents of .shellcheckrc for the given file
518- getConfig cache filename =
518+ -- Returns the name and contents of .shellcheckrc for the given file,
519+ -- merged with any shellcheck.* directives found in applicable
520+ -- EditorConfig files.
521+ getConfig cache filename = do
522+ rcResult <- getRcConfig cache filename
523+ ecResult <- getEditorConfig filename
524+ return $ mergeConfigs filename rcResult ecResult
525+
526+ mergeConfigs filename rcResult ecResult =
527+ case (rcResult, ecResult) of
528+ (Nothing , Nothing ) -> Nothing
529+ (Just (rcPath, rc), Nothing ) -> Just (rcPath, rc)
530+ (Nothing , Just (ecPath, ec)) -> Just (ecPath, ec)
531+ (Just (rcPath, rc), Just (_, ec)) -> Just (rcPath, rc ++ " \n " ++ ec)
532+
533+ getRcConfig cache filename =
519534 case rcfile options of
520535 Just file -> do
521536 -- We have a specified rcfile. Ignore normal rcfile resolution.
@@ -541,6 +556,63 @@ ioInterface options files = do
541556 writeIORef cache (dir, result)
542557 return result
543558
559+ -- Look for .editorconfig files in the target file's directory and
560+ -- all its parents (as per the EditorConfig spec), plus the global
561+ -- ${XDG_CONFIG_HOME}/editorconfig.ini default. shellcheck.* keys in
562+ -- matching sections are turned into directives.
563+ getEditorConfig filename = do
564+ -- Resolve the directory (to find .editorconfig files) but keep
565+ -- the leaf filename as-is so that globs match the symlink name
566+ -- rather than the resolved target.
567+ let name = takeFileName filename
568+ dir <- normalize (takeDirectory filename)
569+ let path = dir </> name
570+ dirConfigs <- collectDirConfigs dir
571+ globalConfig <- readGlobalEditorConfig
572+ let allConfigs = dirConfigs ++ globalConfig
573+ contributions = concatMap (directivesFor path) allConfigs
574+ return $ case contributions of
575+ [] -> Nothing
576+ ((sourceFile, _): _) -> Just (sourceFile, concatMap snd contributions)
577+ where
578+ -- For each EditorConfig file: either report an invalid 'root'
579+ -- declaration at its line, or yield the matching shellcheck.*
580+ -- directives. Invalid roots take priority over directives.
581+ directivesFor path (file, contents) =
582+ case invalidRootLines contents of
583+ (badLine: _) ->
584+ [(file, replicate (badLine - 1 ) ' \n ' ++ " invalid editorconfig value\n " )]
585+ [] ->
586+ let relative = makeRelativeTo (takeDirectory file) path
587+ result = parseEditorConfig contents relative
588+ in if null result then [] else [(file, result)]
589+
590+ makeRelativeTo dir path =
591+ case stripPrefix (addTrailingSlash dir) path of
592+ Just rest -> rest
593+ Nothing -> takeFileName path
594+
595+ addTrailingSlash dir
596+ | null dir = dir
597+ | last dir == ' /' = dir
598+ | otherwise = dir ++ " /"
599+
600+ collectDirConfigs dir = do
601+ current <- readConfig (dir </> " .editorconfig" )
602+ let isRoot = maybe False (isEditorConfigRoot . snd ) current
603+ next = takeDirectory dir
604+ rest <- if next /= dir && not isRoot
605+ then collectDirConfigs next
606+ else return []
607+ return $ maybeToList current ++ rest
608+
609+ readGlobalEditorConfig = do
610+ path <- (getXdgDirectory XdgConfig " editorconfig.ini" )
611+ `catch` ((const $ return " " ) :: IOException -> IO FilePath )
612+ if null path
613+ then return []
614+ else maybeToList <$> readConfig path
615+
544616 findConfig paths =
545617 case paths of
546618 (file: rest) -> do
0 commit comments