From 50af8aba29199ae3e008e38de1bc9463ca1777f8 Mon Sep 17 00:00:00 2001 From: Benjamin Gordon Date: Tue, 7 May 2019 15:49:34 -0600 Subject: [PATCH] Add json1 format that ignores tabs The new json1 format works just like json except that it treats tabs as single characters instead of 8-character tabstops. The main use case is to allow editors to pass -fjson1 so that they can consume the json output in a character-oriented way without breaking backwards compatibility. Also addresses #1048. --- CHANGELOG.md | 1 + shellcheck.1.md | 7 ++++++- shellcheck.hs | 3 ++- src/ShellCheck/Formatter/Format.hs | 8 +++++++- src/ShellCheck/Formatter/JSON.hs | 21 +++++++++++++++++---- 5 files changed, 33 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bb623f0..797bf67 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ Enable with `-o` flags or `enable=name` directives - Source paths: Use `-P dir1:dir2` or a `source-path=dir1` directive to specify search paths for sourced files. +- json1 format like --format=json but treats tabs as single characters - SC2249: Warn about `case` with missing default case (verbose) - SC2248: Warn about unquoted variables without special chars (verbose) - SC2247: Warn about $"(cmd)" and $"{var}" diff --git a/shellcheck.1.md b/shellcheck.1.md index f8cbd44..e99e8ea 100644 --- a/shellcheck.1.md +++ b/shellcheck.1.md @@ -153,7 +153,7 @@ not warn at all, as `ksh` supports decimals in arithmetic contexts. : Json is a popular serialization format that is more suitable for web applications. ShellCheck's json is compact and contains only the bare - minimum. + minimum. Tabs are 8 characters. [ { @@ -167,6 +167,11 @@ not warn at all, as `ksh` supports decimals in arithmetic contexts. ... ] +**json1** + +: This is the same as shellcheck's json format, but tabs are treated as + single characters instead of 8-character tabstops. + *quiet* : Suppress all normal output. Exit with zero if no issues are found, diff --git a/shellcheck.hs b/shellcheck.hs index c8ea7fb..cc5050e 100644 --- a/shellcheck.hs +++ b/shellcheck.hs @@ -141,7 +141,8 @@ formats :: FormatterOptions -> Map.Map String (IO Formatter) formats options = Map.fromList [ ("checkstyle", ShellCheck.Formatter.CheckStyle.format), ("gcc", ShellCheck.Formatter.GCC.format), - ("json", ShellCheck.Formatter.JSON.format), + ("json", ShellCheck.Formatter.JSON.format False), -- JSON with 8-char tabs + ("json1", ShellCheck.Formatter.JSON.format True), -- JSON with 1-char tabs ("tty", ShellCheck.Formatter.TTY.format options), ("quiet", ShellCheck.Formatter.Quiet.format options) ] diff --git a/src/ShellCheck/Formatter/Format.hs b/src/ShellCheck/Formatter/Format.hs index 57b9d71..4a3908a 100644 --- a/src/ShellCheck/Formatter/Format.hs +++ b/src/ShellCheck/Formatter/Format.hs @@ -22,6 +22,7 @@ module ShellCheck.Formatter.Format where import ShellCheck.Data import ShellCheck.Interface import ShellCheck.Fixer +import Control.Monad import Data.Array -- A formatter that carries along an arbitrary piece of data @@ -54,5 +55,10 @@ makeNonVirtual comments contents = where list = lines contents arr = listArray (1, length list) list - fix c = removeTabStops c arr + untabbedFix f = newFix { + fixReplacements = map (\r -> removeTabStops r arr) (fixReplacements f) + } + fix c = (removeTabStops c arr) { + pcFix = liftM untabbedFix (pcFix c) + } diff --git a/src/ShellCheck/Formatter/JSON.hs b/src/ShellCheck/Formatter/JSON.hs index 02a549d..72f90fa 100644 --- a/src/ShellCheck/Formatter/JSON.hs +++ b/src/ShellCheck/Formatter/JSON.hs @@ -30,11 +30,12 @@ import GHC.Exts import System.IO import qualified Data.ByteString.Lazy.Char8 as BL -format = do +format :: Bool -> IO Formatter +format removeTabs = do ref <- newIORef [] return Formatter { header = return (), - onResult = collectResult ref, + onResult = collectResult removeTabs ref, onFailure = outputError, footer = finish ref } @@ -96,8 +97,20 @@ instance ToJSON Fix where ] outputError file msg = hPutStrLn stderr $ file ++ ": " ++ msg -collectResult ref result _ = - modifyIORef ref (\x -> crComments result ++ x) + +collectResult removeTabs ref cr sys = mapM_ f groups + where + comments = crComments cr + groups = groupWith sourceFile comments + f :: [PositionedComment] -> IO () + f group = do + let filename = sourceFile (head group) + result <- siReadFile sys filename + let contents = either (const "") id result + let comments' = if removeTabs + then makeNonVirtual comments contents + else comments + modifyIORef ref (\x -> comments' ++ x) finish ref = do list <- readIORef ref