From 223f92e3144ef60b2060f6648b3d946973d2e489 Mon Sep 17 00:00:00 2001 From: Lars Jellema Date: Mon, 26 Aug 2019 14:04:19 +0200 Subject: [PATCH 1/2] Fix accidental escaping of interpolations --- src/Nixfmt/Parser.hs | 4 +-- src/Nixfmt/Pretty.hs | 66 ++++++++++++++++++++++++++++++++------------ 2 files changed, 51 insertions(+), 19 deletions(-) diff --git a/src/Nixfmt/Parser.hs b/src/Nixfmt/Parser.hs index a8c72a60..49047576 100644 --- a/src/Nixfmt/Parser.hs +++ b/src/Nixfmt/Parser.hs @@ -102,7 +102,7 @@ simpleStringPart = TextPart <$> someText ( chunk "\\r" *> pure "\r" <|> chunk "\\t" *> pure "\t" <|> chunk "\\" *> (Text.singleton <$> anySingle) <|> - chunk "$$" <> manyP (=='$') <|> + chunk "$$" <|> try (chunk "$" <* notFollowedBy (char '{')) <|> someP (\t -> t /= '"' && t /= '\\' && t /= '$')) @@ -114,7 +114,7 @@ indentedStringPart = TextPart <$> someText ( chunk "''\\" *> (Text.singleton <$> anySingle) <|> chunk "''$" *> pure "$" <|> chunk "'''" *> pure "''" <|> - chunk "$$" <> manyP (=='$') <|> + chunk "$$" <|> try (chunk "$" <* notFollowedBy (char '{')) <|> try (chunk "'" <* notFollowedBy (char '\'')) <|> someP (\t -> t /= '\'' && t /= '$' && t /= '\n')) diff --git a/src/Nixfmt/Pretty.hs b/src/Nixfmt/Pretty.hs index 5d086267..c491a554 100644 --- a/src/Nixfmt/Pretty.hs +++ b/src/Nixfmt/Pretty.hs @@ -12,9 +12,9 @@ import Prelude hiding (String) import Data.Char (isSpace) import Data.Maybe (fromMaybe) -import Data.Text (Text, isPrefixOf, stripPrefix) +import Data.Text (Text, isPrefixOf, isSuffixOf, stripPrefix) import qualified Data.Text as Text - (empty, isInfixOf, last, null, replace, strip, takeWhile) + (dropEnd, empty, init, isInfixOf, last, null, replace, strip, takeWhile) import Nixfmt.Predoc (Doc, Pretty, base, emptyline, group, hardline, hardspace, hcat, line, line', @@ -302,7 +302,7 @@ isIndented parts = isEmptyLine :: [StringPart] -> Bool isEmptyLine [] = True -isEmptyLine [TextPart t] = Text.strip t == Text.empty +isEmptyLine [TextPart t] = Text.null $ Text.strip t isEmptyLine _ = False isSimpleString :: [[StringPart]] -> Bool @@ -347,26 +347,58 @@ instance Pretty [[StringPart]] where | isSimpleString parts = prettySimpleString parts | otherwise = prettyIndentedString parts +type UnescapeInterpol = Text -> Text +type EscapeText = Text -> Text + +prettyLine :: EscapeText -> UnescapeInterpol -> [StringPart] -> Doc +prettyLine escapeText unescapeInterpol + = pretty . unescapeInterpols . map escape + where escape (TextPart t) = TextPart (escapeText t) + escape x = x + + unescapeInterpols [] = [] + unescapeInterpols (TextPart t : TextPart u : xs) + = unescapeInterpols (TextPart (t <> u) : xs) + unescapeInterpols (TextPart t : xs@(Interpolation _ _ _ : _)) + = TextPart (unescapeInterpol t) : unescapeInterpols xs + unescapeInterpols (x : xs) = x : unescapeInterpols xs + prettySimpleString :: [[StringPart]] -> Doc prettySimpleString parts = group $ text "\"" - <> (sepBy (text "\\n") (map (pretty . map escape) parts)) + <> (sepBy (text "\\n") (map (prettyLine escape unescapeInterpol) parts)) <> text "\"" - where escape (TextPart t) = TextPart - $ Text.replace "$\\${" "$${" - $ Text.replace "${" "\\${" - $ Text.replace "\"" "\\\"" - $ Text.replace "\r" "\\r" - $ Text.replace "\\" "\\\\" t - escape x = x + where escape + = Text.replace "$\\${" "$${" + . Text.replace "${" "\\${" + . Text.replace "\"" "\\\"" + . Text.replace "\r" "\\r" + . Text.replace "\\" "\\\\" + + unescapeInterpol t + | "$" `isSuffixOf` t = Text.init t <> "\\$" + | otherwise = t prettyIndentedString :: [[StringPart]] -> Doc prettyIndentedString parts = group $ base $ text "''" <> line' - <> nest 2 (sepBy newline (map (pretty . map escape) parts)) + <> nest 2 (sepBy newline (map (prettyLine escape unescapeInterpol) parts)) <> text "''" - where escape (TextPart t) = TextPart - $ Text.replace "$''${" "$${" - $ Text.replace "${" "''${" - $ Text.replace "''" "'''" t - escape x = x + where escape + = Text.replace "$''${" "$${" + . Text.replace "${" "''${" + . Text.replace "''" "'''" + + unescapeInterpol t + | Text.null t = t + | Text.last t /= '$' = t + | trailingQuotes (Text.init t) `mod` 3 == 0 + = Text.init t <> "''$" + | trailingQuotes (Text.init t) `mod` 3 == 1 + = Text.dropEnd 2 t <> "''\\'''$" + | otherwise + = error "should never happen after escape" + + trailingQuotes t + | "'" `isSuffixOf` t = 1 + trailingQuotes (Text.init t) + | otherwise = 0 :: Int From 102f47f20de819d640c1483c8c6fa7e3712121fc Mon Sep 17 00:00:00 2001 From: Lars Jellema Date: Mon, 26 Aug 2019 16:18:14 +0200 Subject: [PATCH 2/2] Prevent paths from being parsed as selectors --- src/Nixfmt/Parser.hs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Nixfmt/Parser.hs b/src/Nixfmt/Parser.hs index 49047576..29e0aa7e 100644 --- a/src/Nixfmt/Parser.hs +++ b/src/Nixfmt/Parser.hs @@ -200,7 +200,8 @@ parens = Parenthesized <$> symbol TParenOpen <*> expression <*> symbol TParenClose selector :: Maybe (Parser Leaf) -> Parser Selector -selector parseDot = Selector <$> sequence parseDot <*> +selector parseDot = Selector <$> + sequence parseDot <* notFollowedBy path <*> ((IDSelector <$> identifier) <|> (InterpolSelector <$> lexeme interpolation) <|> (StringSelector <$> lexeme simpleString)) <*>