diff --git a/CHANGELOG.md b/CHANGELOG.md index 26e538c3..fe3030e9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,10 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) a ## [Unreleased] +## [2.4.1] - 2022-05-10 + +- [#73](https://github.com/ruby-syntax-tree/syntax_tree/pull/73) - Fix nested hash patterns from accidentally adding a `then` to their output. + ## [2.4.0] - 2022-05-07 ### Added @@ -209,7 +213,8 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) a - 🎉 Initial release! 🎉 -[unreleased]: https://github.com/ruby-syntax-tree/syntax_tree/compare/v2.4.0...HEAD +[unreleased]: https://github.com/ruby-syntax-tree/syntax_tree/compare/v2.4.1...HEAD +[2.4.1]: https://github.com/ruby-syntax-tree/syntax_tree/compare/v2.4.0...v2.4.1 [2.4.0]: https://github.com/ruby-syntax-tree/syntax_tree/compare/v2.3.1...v2.4.0 [2.3.1]: https://github.com/ruby-syntax-tree/syntax_tree/compare/v2.3.0...v2.3.1 [2.3.0]: https://github.com/ruby-syntax-tree/syntax_tree/compare/v2.2.0...v2.3.0 diff --git a/Gemfile.lock b/Gemfile.lock index 8357fd92..b4eebdd4 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,7 +1,7 @@ PATH remote: . specs: - syntax_tree (2.4.0) + syntax_tree (2.4.1) GEM remote: https://rubygems.org/ diff --git a/README.md b/README.md index b0c916bd..8955a310 100644 --- a/README.md +++ b/README.md @@ -404,7 +404,17 @@ The language server additionally includes this custom request to return a textua ## Plugins -You can register additional languages that can flow through the same CLI with Syntax Tree's plugin system. To register a new language, call: +You can register additional configuration and additional languages that can flow through the same CLI with Syntax Tree's plugin system. When invoking the CLI, you pass through the list of plugins with the `--plugins` options to the commands that accept them. They should be a comma-delimited list. When the CLI first starts, it will require the files corresponding to those names. + +### Configuration + +To register additional configuration, define a file somewhere in your load path named `syntax_tree/my_plugin` directory. Then when invoking the CLI, you will pass `--plugins=my_plugin`. That will get required. In this way, you can modify Syntax Tree however you would like. Some plugins ship with Syntax Tree itself. They are: + +* `plugin/single_quotes` - This will change all of your string literals to use single quotes instead of the default double quotes. + +### Languages + +To register a new language, call: ```ruby SyntaxTree.register_handler(".mylang", MyLanguage) @@ -416,13 +426,11 @@ In this case, whenever the CLI encounters a filepath that ends with the given ex * `MyLanguage.parse(source)` - this should return the syntax tree corresponding to the given source. Those objects should implement the `pretty_print` interface. * `MyLanguage.format(source)` - this should return the formatted version of the given source. -Below are listed all of the "official" plugins hosted under the same GitHub organization, which can be used as references for how to implement other plugins. - -* [SyntaxTree::Haml](https://github.com/ruby-syntax-tree/syntax_tree-haml) for the [Haml template language](https://haml.info/). -* [SyntaxTree::JSON](https://github.com/ruby-syntax-tree/syntax_tree-json) for JSON. -* [SyntaxTree::RBS](https://github.com/ruby-syntax-tree/syntax_tree-rbs) for the [RBS type language](https://github.com/ruby/rbs). +Below are listed all of the "official" language plugins hosted under the same GitHub organization, which can be used as references for how to implement other plugins. -When invoking the CLI, you pass through the list of plugins with the `--plugins` options to the commands that accept them. They should be a comma-delimited list. When the CLI first starts, it will require the files corresponding to those names. +* [haml](https://github.com/ruby-syntax-tree/syntax_tree-haml) for the [Haml template language](https://haml.info/). +* [json](https://github.com/ruby-syntax-tree/syntax_tree-json) for JSON. +* [rbs](https://github.com/ruby-syntax-tree/syntax_tree-rbs) for the [RBS type language](https://github.com/ruby/rbs). ## Integration diff --git a/lib/syntax_tree/formatter/single_quotes.rb b/lib/syntax_tree/formatter/single_quotes.rb new file mode 100644 index 00000000..4d1f41b3 --- /dev/null +++ b/lib/syntax_tree/formatter/single_quotes.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +module SyntaxTree + class Formatter + # This module overrides the quote method on the formatter to use single + # quotes for everything instead of double quotes. + module SingleQuotes + def quote + "'" + end + end + end +end diff --git a/lib/syntax_tree/node.rb b/lib/syntax_tree/node.rb index 07bafb00..fdb40631 100644 --- a/lib/syntax_tree/node.rb +++ b/lib/syntax_tree/node.rb @@ -3904,7 +3904,7 @@ module Quotes # quotes, then single quotes would deactivate it.) def self.locked?(node) node.parts.any? do |part| - part.is_a?(TStringContent) && part.value.match?(/\\|#[@${]/) + !part.is_a?(TStringContent) || part.value.match?(/\\|#[@${]/) end end @@ -5064,13 +5064,16 @@ def format(q) parts = keywords.map { |(key, value)| KeywordFormatter.new(key, value) } parts << KeywordRestFormatter.new(keyword_rest) if keyword_rest + nested = PATTERNS.include?(q.parent.class) contents = -> do q.group { q.seplist(parts) { |part| q.format(part, stackable: false) } } # If there isn't a constant, and there's a blank keyword_rest, then we # have an plain ** that needs to have a `then` after it in order to # parse correctly on the next parse. - q.text(" then") if !constant && keyword_rest && keyword_rest.value.nil? + if !constant && keyword_rest && keyword_rest.value.nil? && !nested + q.text(" then") + end end # If there is a constant, we're going to format to have the constant name @@ -5097,7 +5100,7 @@ def format(q) # If there's only one pair, then we'll just print the contents provided # we're not inside another pattern. - if !PATTERNS.include?(q.parent.class) && parts.size == 1 + if !nested && parts.size == 1 contents.call return end diff --git a/lib/syntax_tree/parser.rb b/lib/syntax_tree/parser.rb index 75d3c322..f5ffe47d 100644 --- a/lib/syntax_tree/parser.rb +++ b/lib/syntax_tree/parser.rb @@ -1671,9 +1671,15 @@ def on_heredoc_end(value) # (nil | VarField) keyword_rest # ) -> HshPtn def on_hshptn(constant, keywords, keyword_rest) - # Create an artificial VarField if we find an extra ** on the end - if !keyword_rest && (token = find_token(Op, "**", consume: false)) + if keyword_rest + # We're doing this to delete the token from the list so that it doesn't + # confuse future patterns by thinking they have an extra ** on the end. + find_token(Op, "**") + elsif (token = find_token(Op, "**", consume: false)) tokens.delete(token) + + # Create an artificial VarField if we find an extra ** on the end. This + # means the formatting will be a little more consistent. keyword_rest = VarField.new(value: nil, location: token.location) end diff --git a/lib/syntax_tree/plugin/single_quotes.rb b/lib/syntax_tree/plugin/single_quotes.rb new file mode 100644 index 00000000..d8034084 --- /dev/null +++ b/lib/syntax_tree/plugin/single_quotes.rb @@ -0,0 +1,4 @@ +# frozen_string_literal: true + +require "syntax_tree/formatter/single_quotes" +SyntaxTree::Formatter.prepend(SyntaxTree::Formatter::SingleQuotes) diff --git a/lib/syntax_tree/version.rb b/lib/syntax_tree/version.rb index 894ff1b7..fbecb604 100644 --- a/lib/syntax_tree/version.rb +++ b/lib/syntax_tree/version.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true module SyntaxTree - VERSION = "2.4.0" + VERSION = "2.4.1" end diff --git a/test/fixtures/hshptn.rb b/test/fixtures/hshptn.rb index 2935f9c1..7a35b4d0 100644 --- a/test/fixtures/hshptn.rb +++ b/test/fixtures/hshptn.rb @@ -66,3 +66,8 @@ case foo in **nil end +% +case foo +in bar, { baz:, **nil } +in qux: +end diff --git a/test/formatter/single_quotes_test.rb b/test/formatter/single_quotes_test.rb new file mode 100644 index 00000000..8bf82cb8 --- /dev/null +++ b/test/formatter/single_quotes_test.rb @@ -0,0 +1,45 @@ +# frozen_string_literal: true + +require_relative "../test_helper" +require "syntax_tree/formatter/single_quotes" + +module SyntaxTree + class Formatter + class TestFormatter < Formatter + prepend Formatter::SingleQuotes + end + + def test_empty_string_literal + assert_format("''\n", "\"\"") + end + + def test_string_literal + assert_format("'string'\n", "\"string\"") + end + + def test_string_literal_with_interpolation + assert_format("\"\#{foo}\"\n") + end + + def test_dyna_symbol + assert_format(":'symbol'\n", ":\"symbol\"") + end + + def test_label + assert_format( + "{ foo => foo, :'bar' => bar }\n", + "{ foo => foo, \"bar\": bar }" + ) + end + + private + + def assert_format(expected, source = expected) + formatter = TestFormatter.new(source, []) + SyntaxTree.parse(source).format(formatter) + + formatter.flush + assert_equal(expected, formatter.output.join) + end + end +end