mirror of
				https://github.com/koalaman/shellcheck.git
				synced 2025-10-31 22:52:50 +08:00 
			
		
		
		
	Move fix application logic to separate module
This commit is contained in:
		
							
								
								
									
										67
									
								
								src/ShellCheck/Fixer.hs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										67
									
								
								src/ShellCheck/Fixer.hs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,67 @@ | |||||||
|  | module ShellCheck.Fixer (applyFix , replaceMultiLines) where | ||||||
|  |  | ||||||
|  | import ShellCheck.Interface | ||||||
|  |  | ||||||
|  | import Data.List | ||||||
|  |  | ||||||
|  | applyFix fix fileLines = | ||||||
|  |     -- apply replacements in sorted order by end position | ||||||
|  |     -- assert no overlaps, or maybe remove overlaps? | ||||||
|  |     let sorted = (reverse . sort) (fixReplacements fix) in | ||||||
|  |     applyReplacement sorted fileLines | ||||||
|  |     where | ||||||
|  |         applyReplacement [] s = s | ||||||
|  |         applyReplacement (rep:xs) s = applyReplacement xs $ replaceMultiLines rep s | ||||||
|  |  | ||||||
|  | -- A replacement that spans multiple line is applied by: | ||||||
|  | -- 1. merging the affected lines into a single string using `unlines` | ||||||
|  | -- 2. apply the replacement as if it only spanned a single line | ||||||
|  | -- The tricky part is adjusting the end column of the replacement | ||||||
|  | -- (the end line doesn't matter because there is only one line) | ||||||
|  | -- | ||||||
|  | --   aaS  <--- start of replacement (row 1 column 3) | ||||||
|  | --   bbbb | ||||||
|  | --   cEc | ||||||
|  | --    \------- end of replacement (row 3 column 2) | ||||||
|  | -- | ||||||
|  | -- a flattened string will look like: | ||||||
|  | -- | ||||||
|  | --   "aaS\nbbbb\ncEc\n" | ||||||
|  | -- | ||||||
|  | -- The column of E has to be adjusted by: | ||||||
|  | -- 1. lengths of lines to be replaced, except the end row itself | ||||||
|  | -- 2. end column of the replacement | ||||||
|  | -- 3. number of '\n' by `unlines` | ||||||
|  | -- Returns the original lines from the file with the replacement applied. | ||||||
|  | -- Multiline replacements completely overwrite new lines in the original string. | ||||||
|  | -- e.g. if the replacement spans 2 lines, but the replacement string does not | ||||||
|  | -- have a '\n', then the number of replaced lines will be 1 shorter. | ||||||
|  | replaceMultiLines rep fileLines = -- this can replace doReplace | ||||||
|  |     let startRow = fromIntegral $ (posLine . repStartPos) rep | ||||||
|  |         endRow =  fromIntegral $ (posLine . repEndPos) rep | ||||||
|  |         (ys, zs) = splitAt endRow fileLines | ||||||
|  |         (xs, toReplaceLines) = splitAt (startRow-1) ys | ||||||
|  |         lengths = fromIntegral $ sum (map length (init toReplaceLines)) | ||||||
|  |         newlines = fromIntegral $ (length toReplaceLines - 1) -- for the '\n' from unlines | ||||||
|  |         original = unlines toReplaceLines | ||||||
|  |         startCol = ((posColumn . repStartPos) rep) | ||||||
|  |         endCol = ((posColumn . repEndPos) rep + newlines + lengths) | ||||||
|  |         replacedLines = (lines $ doReplace startCol endCol original (repString rep)) | ||||||
|  |     in | ||||||
|  |     xs ++ replacedLines ++ zs | ||||||
|  |  | ||||||
|  | -- FIXME: Work correctly with tabs | ||||||
|  | -- start and end comes from pos, which is 1 based | ||||||
|  | -- doReplace 0 0 "1234" "A" -> "A1234" -- technically not valid | ||||||
|  | -- doReplace 1 1 "1234" "A" -> "A1234" | ||||||
|  | -- doReplace 1 2 "1234" "A" -> "A234" | ||||||
|  | -- doReplace 3 3 "1234" "A" -> "12A34" | ||||||
|  | -- doReplace 4 4 "1234" "A" -> "123A4" | ||||||
|  | -- doReplace 5 5 "1234" "A" -> "1234A" | ||||||
|  | doReplace start end o r = | ||||||
|  |     let si = fromIntegral (start-1) | ||||||
|  |         ei = fromIntegral (end-1) | ||||||
|  |         (x, xs) = splitAt si o | ||||||
|  |         (y, z) = splitAt (ei - si) xs | ||||||
|  |     in | ||||||
|  |     x ++ r ++ z | ||||||
| @@ -19,6 +19,7 @@ | |||||||
| -} | -} | ||||||
| module ShellCheck.Formatter.TTY (format) where | module ShellCheck.Formatter.TTY (format) where | ||||||
|  |  | ||||||
|  | import ShellCheck.Fixer | ||||||
| import ShellCheck.Interface | import ShellCheck.Interface | ||||||
| import ShellCheck.Formatter.Format | import ShellCheck.Formatter.Format | ||||||
|  |  | ||||||
| @@ -154,88 +155,18 @@ showFixedString color comments lineNum fileLines = | |||||||
|  |  | ||||||
| fixedString :: PositionedComment -> [String] -> [String] | fixedString :: PositionedComment -> [String] -> [String] | ||||||
| fixedString comment fileLines = | fixedString comment fileLines = | ||||||
|     let lineNum = lineNo comment |  | ||||||
|         line = fileLines !! fromIntegral (lineNum - 1) in |  | ||||||
|     case (pcFix comment) of |     case (pcFix comment) of | ||||||
|     Nothing -> [""] |     Nothing -> [""] | ||||||
|     Just rs -> |     Just fix -> | ||||||
|         -- apply replacements in sorted order by end position |         let (start, end) = affectedRange (fixReplacements fix) in | ||||||
|         -- assert no overlaps, or maybe remove overlaps? |  | ||||||
|         let sorted = (reverse . sort) (fixReplacements rs) |  | ||||||
|             (start, end) = calculateOverlap sorted 1 1 |  | ||||||
|         in |  | ||||||
|         -- applyReplacement returns the full update file, we really only care about the changed lines |         -- applyReplacement returns the full update file, we really only care about the changed lines | ||||||
|         -- so we calculate overlapping lines using replacements |         -- so we calculate overlapping lines using replacements | ||||||
|         -- TODO separate this logic of printing affected lines out |         drop start $ take end $ applyFix fix fileLines | ||||||
|         -- since for some output we might want to have the full file output |  | ||||||
|         drop (fromIntegral start) $ take (fromIntegral end) $ applyReplacement sorted fileLines |  | ||||||
|         where |         where | ||||||
|             applyReplacement [] s = s |             affectedRange rs = _affectedRange rs 1 1 | ||||||
|             applyReplacement (rep:xs) s = |             _affectedRange [] s e = (fromIntegral s, fromIntegral e) | ||||||
|                 let result = replaceMultiLines rep s |             _affectedRange (rep:xs) s e = | ||||||
|                 in |                 _affectedRange xs (min s (posLine (repStartPos rep))) (max e (posLine (repEndPos rep))) | ||||||
|                 applyReplacement xs result |  | ||||||
|             calculateOverlap [] s e = (s, e) |  | ||||||
|             calculateOverlap (rep:xs) s e = |  | ||||||
|                 calculateOverlap xs (min s (posLine (repStartPos rep))) (max e (posLine (repEndPos rep))) |  | ||||||
|  |  | ||||||
| -- A replacement that spans multiple line is applied by: |  | ||||||
| -- 1. merging the affected lines into a single string using `unlines` |  | ||||||
| -- 2. apply the replacement as if it only spanned a single line |  | ||||||
| -- The tricky part is adjusting the end column of the replacement |  | ||||||
| -- (the end line doesn't matter because there is only one line) |  | ||||||
| -- |  | ||||||
| --   aaS  <--- start of replacement (row 1 column 3) |  | ||||||
| --   bbbb |  | ||||||
| --   cEc |  | ||||||
| --    \------- end of replacement (row 3 column 2) |  | ||||||
| -- |  | ||||||
| -- a flattened string will look like: |  | ||||||
| -- |  | ||||||
| --   "aaS\nbbbb\ncEc\n" |  | ||||||
| -- |  | ||||||
| -- The column of E has to be adjusted by: |  | ||||||
| -- 1. lengths of lines to be replaced, except the end row itself |  | ||||||
| -- 2. end column of the replacement |  | ||||||
| -- 3. number of '\n' by `unlines` |  | ||||||
| -- Returns the original lines from the file with the replacement applied. |  | ||||||
| -- Multiline replacements completely overwrite new lines in the original string. |  | ||||||
| -- e.g. if the replacement spans 2 lines, but the replacement string does not |  | ||||||
| -- have a '\n', then the number of replaced lines will be 1 shorter. |  | ||||||
| replaceMultiLines rep fileLines = -- this can replace doReplace |  | ||||||
|     let startRow = fromIntegral $ (posLine . repStartPos) rep |  | ||||||
|         endRow =  fromIntegral $ (posLine . repEndPos) rep |  | ||||||
|         (ys, zs) = splitAt endRow fileLines |  | ||||||
|         (xs, toReplaceLines) = splitAt (startRow-1) ys |  | ||||||
|         lengths = fromIntegral $ sum (map length (init toReplaceLines)) |  | ||||||
|         newlines = fromIntegral $ (length toReplaceLines - 1) -- for the '\n' from unlines |  | ||||||
|         original = unlines toReplaceLines |  | ||||||
|         startCol = ((posColumn . repStartPos) rep) |  | ||||||
|         endCol = ((posColumn . repEndPos) rep + newlines + lengths) |  | ||||||
|         replacedLines = (lines $ doReplace startCol endCol original (repString rep)) |  | ||||||
|     in |  | ||||||
|     xs ++ replacedLines ++ zs |  | ||||||
|  |  | ||||||
| -- FIXME: Work correctly with tabs |  | ||||||
| -- start and end comes from pos, which is 1 based |  | ||||||
| -- doReplace 0 0 "1234" "A" -> "A1234" -- technically not valid |  | ||||||
| -- doReplace 1 1 "1234" "A" -> "A1234" |  | ||||||
| -- doReplace 1 2 "1234" "A" -> "A234" |  | ||||||
| -- doReplace 3 3 "1234" "A" -> "12A34" |  | ||||||
| -- doReplace 4 4 "1234" "A" -> "123A4" |  | ||||||
| -- doReplace 5 5 "1234" "A" -> "1234A" |  | ||||||
| doReplace start end o r = |  | ||||||
|     let si = fromIntegral (start-1) |  | ||||||
|         ei = fromIntegral (end-1) |  | ||||||
|         (x, xs) = splitAt si o |  | ||||||
|         (y, z) = splitAt (ei - si) xs |  | ||||||
|     in |  | ||||||
|     x ++ r ++ z |  | ||||||
|  |  | ||||||
| start = newPosition { posLine = 2, posColumn = 3 } |  | ||||||
| end = newPosition { posLine = 2, posColumn = 4 } |  | ||||||
| r = newReplacement { repStartPos = start, repEndPos = end, repString = "hello" } |  | ||||||
| filelines = ["first", "second", "third", "fourth"] |  | ||||||
|  |  | ||||||
| cuteIndent :: PositionedComment -> String | cuteIndent :: PositionedComment -> String | ||||||
| cuteIndent comment = | cuteIndent comment = | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user