From ff6ee21db94fdd203a23d6c1e91afaea35fec7a0 Mon Sep 17 00:00:00 2001 From: Joe Hansche Date: Wed, 29 Jul 2015 20:48:53 -0400 Subject: [PATCH 1/4] Add Gherkin grammar support This resolves Glavin001/atom-beautify#377 Uses the https://github.com/cucumber/gherkin/tree/master/js node.js package to use the official Lexer class. The only known issue I've seen so far is that it does not format tables properly -- the columns are not resized to match the widest cell in each column. --- package.json | 1 + src/beautifiers/gherkin.coffee | 129 +++++++++++++++++++++++++++++++++ src/beautifiers/index.coffee | 1 + src/languages/gherkin.coffee | 37 ++++++++++ src/languages/index.coffee | 1 + 5 files changed, 169 insertions(+) create mode 100644 src/beautifiers/gherkin.coffee create mode 100644 src/languages/gherkin.coffee diff --git a/package.json b/package.json index d613653..0b54653 100644 --- a/package.json +++ b/package.json @@ -71,6 +71,7 @@ "event-kit": "^1.2.0", "expand-home-dir": "0.0.2", "extend": "^2.0.1", + "gherkin": "2.12.2", "handlebars": "^3.0.3", "js-beautify": "^1.5.7", "jscs": "^1.13.1", diff --git a/src/beautifiers/gherkin.coffee b/src/beautifiers/gherkin.coffee new file mode 100644 index 0000000..7717c0b --- /dev/null +++ b/src/beautifiers/gherkin.coffee @@ -0,0 +1,129 @@ +### +### + +"use strict" +Beautifier = require('./beautifier') +Lexer = require('gherkin').Lexer('en') +logger = require('../logger')(__filename) + +module.exports = class Gherkin extends Beautifier + name: "Gherkin formatter" + + options: { + gherkin: true + } + + beautify: (text, language, options) -> + return new @Promise((resolve, reject) -> + recorder = { + lines: [] + tags: [] + comments: [] + + last_obj: null + + indent_to: (indent_level = 0) -> + return options.indent_char.repeat(options.indent_size * indent_level) + + write_blank: () -> + @lines.push('') + + write_indented: (content, indent = 0) -> + for line in content.trim().split("\n") + @lines.push("#{@indent_to(indent)}#{line.trim()}") + + write_comments: (indent = 0) -> + for comment in @comments.splice(0, @comments.length) + @write_indented(comment, indent) + + write_tags: (indent = 0) -> + for tag in @tags.splice(0, @tags.length) + @write_indented(tag, indent) + + comment: (value, line) -> + logger.verbose({token: 'comment', value: value.trim(), line: line}) + @comments.push(value) + + tag: (value, line) -> + logger.verbose({token: 'tag', value: value, line: line}) + @tags.push(value) + + feature: (keyword, name, description, line) -> + logger.verbose({token: 'feature', keyword: keyword, name: name, description: description, line: line}) + + @write_comments(0) + @write_tags(0) + @write_indented("#{keyword}: #{name}", '') + @write_indented(description, 1) if description + + background: (keyword, name, description, line) -> + logger.verbose({token: 'background', keyword: keyword, name: name, description: description, line: line}) + + @write_blank() + @write_comments(1) + @write_indented("#{keyword}: #{name}", 1) + @write_indented(description, 2) if description + + scenario: (keyword, name, description, line) -> + logger.verbose({token: 'scenario', keyword: keyword, name: name, description: description, line: line}) + + @write_blank() + @write_comments(1) + @write_tags(1) + @write_indented("#{keyword}: #{name}", 1) + @write_indented(description, 2) if description + + scenario_outline: (keyword, name, description, line) -> + logger.verbose({token: 'outline', keyword: keyword, name: name, description: description, line: line}) + + @write_blank() + @write_comments(1) + @write_tags(1) + @write_indented("#{keyword}: #{name}", 1) + @write_indented(description, 2) if description + + examples: (keyword, name, description, line) -> + logger.verbose({token: 'examples', keyword: keyword, name: name, description: description, line: line}) + + @write_blank() + @write_comments(2) + @write_tags(2) + @write_indented("#{keyword}: #{name}", 2) + @write_indented(description, 3) if description + + step: (keyword, name, line) -> + logger.verbose({token: 'step', keyword: keyword, name: name, line: line}) + + @write_comments(2) + @write_indented("#{keyword}#{name}", 2) + + doc_string: (content_type, string, line) -> + logger.verbose({token: 'doc_string', content_type: content_type, string: string, line: line}) + three_quotes = '"""' + + @write_comments(2) + @write_indented("#{three_quotes}#{content_type}\n#{string}\n#{three_quotes}", 3) + + row: (cells, line) -> + logger.verbose({token: 'row', cells: cells, line: line}) + + # TODO: need to collect rows so that we can align the vertical pipes to the widest columns + # See Gherkin::Formatter::PrettyFormatter#table(rows) + @write_comments(3) + @write_indented("| #{cells.join(' | ')} |", 3) + + eof: () -> + logger.verbose({token: 'eof'}) + # If there were any comments left, treat them as step comments. + @write_comments(2) + } + + lexer = new Lexer(recorder) + lexer.scan(text) + + if options.debug_lexer + for line in recorder.lines + logger.verbose("> #{line}") + + resolve recorder.lines.join("\n") + ) diff --git a/src/beautifiers/index.coffee b/src/beautifiers/index.coffee index d317a4c..ac84254 100644 --- a/src/beautifiers/index.coffee +++ b/src/beautifiers/index.coffee @@ -39,6 +39,7 @@ module.exports = class Beautifiers extends EventEmitter 'coffee-fmt' 'htmlbeautifier' 'csscomb' + 'gherkin' 'gofmt' 'fortran-beautifier' 'js-beautify' diff --git a/src/languages/gherkin.coffee b/src/languages/gherkin.coffee new file mode 100644 index 0000000..33f0ca9 --- /dev/null +++ b/src/languages/gherkin.coffee @@ -0,0 +1,37 @@ +# Get Atom defaults +tabLength = atom?.config.get('editor.tabLength') ? 4 +softTabs = atom?.config.get('editor.softTabs') ? true +defaultIndentSize = (if softTabs then tabLength else 1) +defaultIndentChar = (if softTabs then " " else "\t") +defaultIndentWithTabs = not softTabs + +module.exports = { + + name: "gherkin" + namespace: "gherkin" + + grammars: [ + "Gherkin" + ] + + extensions: [ + "feature" + ] + + options: + indent_size: + type: 'integer' + default: defaultIndentSize + minimum: 0 + description: "Indentation size/length" + indent_char: + type: 'string' + default: defaultIndentChar + minimum: 0 + description: "Indentation character" + debug_lexer: + type: 'boolean' + default: false + description: "Enable debug logs of the Gherkin Lexer" + +} diff --git a/src/languages/index.coffee b/src/languages/index.coffee index 10bad55..5ce2222 100644 --- a/src/languages/index.coffee +++ b/src/languages/index.coffee @@ -22,6 +22,7 @@ module.exports = class Languages "d" "ejs" "erb" + "gherkin" "go" "fortran" "handlebars" From 758fe292537e6646834464a085e5f1b7aa79afb6 Mon Sep 17 00:00:00 2001 From: Joe Hansche Date: Tue, 4 Aug 2015 13:25:26 -0400 Subject: [PATCH 2/4] Fix the way @tags are written out Tags are typically separated by spaces, not newlines. I suppose this could be an option --- src/beautifiers/gherkin.coffee | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/beautifiers/gherkin.coffee b/src/beautifiers/gherkin.coffee index 7717c0b..8c044aa 100644 --- a/src/beautifiers/gherkin.coffee +++ b/src/beautifiers/gherkin.coffee @@ -37,8 +37,8 @@ module.exports = class Gherkin extends Beautifier @write_indented(comment, indent) write_tags: (indent = 0) -> - for tag in @tags.splice(0, @tags.length) - @write_indented(tag, indent) + if @tags.length > 0 + @write_indented(@tags.splice(0, @tags.length).join(' '), indent) comment: (value, line) -> logger.verbose({token: 'comment', value: value.trim(), line: line}) From b13fa00d98702d4df55dde95446af81399a68f60 Mon Sep 17 00:00:00 2001 From: Joe Hansche Date: Tue, 4 Aug 2015 13:26:23 -0400 Subject: [PATCH 3/4] Remove the debug_lexer option, replace with loggerLevel check --- src/beautifiers/gherkin.coffee | 3 ++- src/languages/gherkin.coffee | 5 ----- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/src/beautifiers/gherkin.coffee b/src/beautifiers/gherkin.coffee index 8c044aa..9569cb3 100644 --- a/src/beautifiers/gherkin.coffee +++ b/src/beautifiers/gherkin.coffee @@ -121,7 +121,8 @@ module.exports = class Gherkin extends Beautifier lexer = new Lexer(recorder) lexer.scan(text) - if options.debug_lexer + loggerLevel = atom?.config.get('atom-beautify._loggerLevel') + if loggerLevel is 'verbose' for line in recorder.lines logger.verbose("> #{line}") diff --git a/src/languages/gherkin.coffee b/src/languages/gherkin.coffee index 33f0ca9..bfd9599 100644 --- a/src/languages/gherkin.coffee +++ b/src/languages/gherkin.coffee @@ -29,9 +29,4 @@ module.exports = { default: defaultIndentChar minimum: 0 description: "Indentation character" - debug_lexer: - type: 'boolean' - default: false - description: "Enable debug logs of the Gherkin Lexer" - } From 8ac29a3fcfe114da6d5fa60bcef3d894175fe536 Mon Sep 17 00:00:00 2001 From: Joe Hansche Date: Tue, 4 Aug 2015 13:26:41 -0400 Subject: [PATCH 4/4] Add Gherkin language test(s) --- .../gherkin/expected/tables.feature | 10 ++++++++ .../gherkin/expected/test.feature | 23 +++++++++++++++++++ .../gherkin/original/_tables.feature | 9 ++++++++ .../gherkin/original/test.feature | 23 +++++++++++++++++++ 4 files changed, 65 insertions(+) create mode 100644 examples/nested-jsbeautifyrc/gherkin/expected/tables.feature create mode 100644 examples/nested-jsbeautifyrc/gherkin/expected/test.feature create mode 100644 examples/nested-jsbeautifyrc/gherkin/original/_tables.feature create mode 100644 examples/nested-jsbeautifyrc/gherkin/original/test.feature diff --git a/examples/nested-jsbeautifyrc/gherkin/expected/tables.feature b/examples/nested-jsbeautifyrc/gherkin/expected/tables.feature new file mode 100644 index 0000000..cbeb73c --- /dev/null +++ b/examples/nested-jsbeautifyrc/gherkin/expected/tables.feature @@ -0,0 +1,10 @@ +Feature: Description + + Scenario Outline: This one will have examples + Given the + Then the + + Examples: + | what | then | + | first value | first then | + | second what value | second then value | \ No newline at end of file diff --git a/examples/nested-jsbeautifyrc/gherkin/expected/test.feature b/examples/nested-jsbeautifyrc/gherkin/expected/test.feature new file mode 100644 index 0000000..57c0f8a --- /dev/null +++ b/examples/nested-jsbeautifyrc/gherkin/expected/test.feature @@ -0,0 +1,23 @@ +Feature: The description of the feature that + might need to be wrapped + onto multiple lines + + Scenario: The description of the scenario that + might need to be wrapped + onto multiple lines + Given I am in some state + When I take some action + # And I can add a comment + Then I should see some outcome + + # A comment gets moved above the tag + @tag1 + Scenario: This scenario has a tag + Then I should see some outcome + + @tag1 @tag2 + Scenario: This scenario has two tags + Then I should see some outcome + + Scenario: This scenario does not have any tags + Then I should see some outcome \ No newline at end of file diff --git a/examples/nested-jsbeautifyrc/gherkin/original/_tables.feature b/examples/nested-jsbeautifyrc/gherkin/original/_tables.feature new file mode 100644 index 0000000..038fc33 --- /dev/null +++ b/examples/nested-jsbeautifyrc/gherkin/original/_tables.feature @@ -0,0 +1,9 @@ +Feature: Description + +Scenario Outline: This one will have examples + Given the + Then the + Examples: + | what | then | + | first value | first then| + | second what|second then| \ No newline at end of file diff --git a/examples/nested-jsbeautifyrc/gherkin/original/test.feature b/examples/nested-jsbeautifyrc/gherkin/original/test.feature new file mode 100644 index 0000000..73dddfd --- /dev/null +++ b/examples/nested-jsbeautifyrc/gherkin/original/test.feature @@ -0,0 +1,23 @@ +Feature: The description of the feature that +might need to be wrapped +onto multiple lines + +Scenario: The description of the scenario that +might need to be wrapped +onto multiple lines +Given I am in some state +When I take some action +# And I can add a comment +Then I should see some outcome + +@tag1 +# A comment gets moved above the tag +Scenario: This scenario has a tag +Then I should see some outcome + +@tag1 @tag2 +Scenario: This scenario has two tags +Then I should see some outcome + +Scenario: This scenario does not have any tags +Then I should see some outcome \ No newline at end of file