From 12615d350e142a119bc4dd654e322e4a9dbc8c20 Mon Sep 17 00:00:00 2001 From: Peter Tseng Date: Sat, 19 Feb 2022 05:20:41 +0000 Subject: [PATCH 1/7] bowling, transpose: conform array format to rest of file bowling has `previousRolls` on a single line. transpose has each element of `lines` on its own line. --- exercises/bowling/canonical-data.json | 4 +--- exercises/transpose/canonical-data.json | 4 +++- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/exercises/bowling/canonical-data.json b/exercises/bowling/canonical-data.json index c8e8d13150..3c4a9f5afe 100644 --- a/exercises/bowling/canonical-data.json +++ b/exercises/bowling/canonical-data.json @@ -133,9 +133,7 @@ "description": "last two strikes followed by only last bonus with non strike points", "property": "score", "input": { - "previousRolls": [ - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 10, 0, 1 - ] + "previousRolls": [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 10, 0, 1] }, "expected": 31 }, diff --git a/exercises/transpose/canonical-data.json b/exercises/transpose/canonical-data.json index 8cb13cdd03..14a9dae38b 100644 --- a/exercises/transpose/canonical-data.json +++ b/exercises/transpose/canonical-data.json @@ -64,7 +64,9 @@ "description": "single line", "property": "transpose", "input": { - "lines": ["Single line."] + "lines": [ + "Single line." + ] }, "expected": [ "S", From 74a606ca5d64f8c89eed5fc854a91d26ac383c30 Mon Sep 17 00:00:00 2001 From: Peter Tseng Date: Sat, 19 Feb 2022 05:24:11 +0000 Subject: [PATCH 2/7] add format-array (formats JSON arrays) format-array expresses our per-file preferences for array formatting, in cases where the preference differs from prettier. To see that this is true, the following procedure can be used: 1. Delete or truncate .pretterignore and run prettier on JSON files. prettier will have reformatted them. 2. run format-array. It will restore the files to their original state. --- bin/format-array.rb | 178 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 178 insertions(+) create mode 100644 bin/format-array.rb diff --git a/bin/format-array.rb b/bin/format-array.rb new file mode 100644 index 0000000000..24b5fdbf59 --- /dev/null +++ b/bin/format-array.rb @@ -0,0 +1,178 @@ +require 'json' + +# format-array expresses (and enacts) our per-file preferences for array formatting, +# in cases where the preference differs from prettier. +# +# It has only one mode of operation: +# Run it without any arguments to format the files. + +# format-array was written because we want to keep files consistently formatted, +# while also optimising for human readability. +# +# As part of optimising for human readability, +# some arrays should be formatted such that each element is on its own line, +# whereas some other arrays should be formatted such that the entire array is on one line. +# +# We could not find an existing tool that allows us to specify array formatting how we wanted. + +# The configuration of array formatting preferences. +# +# format can be the following choices: +# +# * single_line (the entire array and all of its elements should be on a single line) +# * multi_line (each element of the array is on its own line) +# * multi_line_unless_single (like multi_line, except arrays with one element remain on a single line) +# * multi_line_deep (multi_line, even applied to arrays within that array) +# * padded (14 elements per line padded to 3 characters per element) +# (TODO: padding amount could be configurable, but we haven't needed it) +formats = { + 'book-store' => { + 'basket' => :single_line, + }, + 'bowling' => { + 'previousRolls' => :single_line, + }, + 'change' => { + 'expected' => :single_line, + }, + 'connect' => { + 'board' => :multi_line, + }, + 'diamond' => { + 'expected' => :multi_line, + }, + 'dominoes' => { + 'dominoes' => :single_line, + }, + 'flatten-array' => { + 'array' => :multi_line_deep, + 'expected' => :multi_line, + }, + 'forth' => { + 'instructions' => :multi_line_unless_single, + }, + 'go-counting' => { + 'board' => :multi_line, + }, + 'minesweeper' => { + 'minefield' => :multi_line_unless_single, + 'expected' => :multi_line_unless_single, + }, + 'ocr-numbers' => { + 'rows' => :multi_line, + }, + 'rectangles' => { + 'strings' => :multi_line_unless_single, + }, + 'saddle-points' => { + 'matrix' => :multi_line, + }, + 'scale-generator' => { + 'expected' => :single_line, + }, + 'sieve' => { + 'expected' => :padded, + }, + 'transpose' => { + 'lines' => :multi_line, + 'expected' => :multi_line, + }, + 'variable-length-quantity' => { + 'integers' => :single_line, + 'expected' => :single_line, + }, + 'word-search' => { + 'grid' => :multi_line, + 'wordsToSearchFor' => :multi_line, + }, +}.each_value(&:freeze).freeze + +def single_line_arrays(contents, key) + # matches things of the form "key": [1, 2, 3] + # because this is not a multi-line regex, + # . does NOT match newlines. + contents.scan(/^( +)"#{key}": (\[.*\],?$)/) +end + +def multi_line_arrays(contents, key) + # matches things of the form + # "key": [ + # 1, + # 2, + # 3 + # ] + # because this IS a multi-line regex (note the /m), + # . DOES match newlines. + # + # To find which closing bracket matches the opening bracket, + # we find the *first* closing bracket that is both: + # - the only thing on its line (except for maybe a comma afterward) + # - at the same indentation level as the key + # + # to find the first of these, make the match non-greedy (*? instead of *) + contents.scan(/^( +)"#{key}": (\[$.*?^\1\],?$)/m) +end + +formats.each { |exercise, exercise_format| + filename = "#{__dir__}/../exercises/#{exercise}/canonical-data.json" + contents = File.read(filename) + exercise_format.each { |key, format_type| + replace = ->(old, new) { + # Include the key in both the search and the replacement, + # to avoid accidentally replacing something we didn't mean to. + # This has been observed to be important for transpose, + # where ["A1"] is both an input and an output. + contents.sub!(%Q("#{key}": #{old}), %Q("#{key}": #{new})) + } + + case format_type + when :single_line + multi_line_arrays(contents, key).each { |indent, arr| + arr_lines = arr.lines + raise "impossible, array doesn't start with [ but instead #{arr_lines[0]}" if arr_lines[0] != "[\n" + raise "impossible, array doesn't end with ] but instead #{arr_lines[-1]}" unless arr_lines[-1].match?(/^ *\],?$/) + replace[arr, "[#{arr_lines[1...-1].map(&:strip).join(' ')}]#{',' if arr[-1] == ','}"] + } + when :multi_line, :multi_line_unless_single + single_line_arrays(contents, key).each { |indent, arr| + elements = JSON.parse(arr.delete_suffix(',')) + next if elements.empty? + next if elements.size == 1 && format_type == :multi_line_unless_single + indented_elements = elements.map { |el| + js = JSON.generate(el) + # JSON.generate will output [1,2,3] but we want [1, 2, 3] + "#{indent} #{el.is_a?(Array) ? js.gsub(',', ', ') : js}" + } + # all lines but the last need a trailing comma + replace[arr, "[\n#{indented_elements.join(",\n")}\n#{indent}]#{',' if arr[-1] == ','}"] + } + when :multi_line_deep + single_line_arrays(contents, key).each { |indent, arr| + # pretty_generate will render an empty array as: + # [ + # + # ] + # whereas we just want [] + pretty_lines = JSON.pretty_generate(JSON.parse(arr)).sub(/\[\s+\]/, '[]').lines + if pretty_lines == ['[]'] + replace[arr, '[]'] + else + raise "impossible, array doesn't start with [ but instead #{pretty_lines[0]}" if pretty_lines[0] != "[\n" + raise "impossible, array doesn't end with ] but instead #{pretty_lines[-1]}" if pretty_lines[-1] != "]" + replace[arr, "[\n" + pretty_lines[1...-1].map { |l| "#{indent}#{l}" }.join + "#{indent}]#{',' if arr[-1] == ','}"] + end + } + when :padded + multi_line_arrays(contents, key).each { |indent, arr| + elements = JSON.parse(arr) + padded_elements = elements.map { |el| '%3d' % el } + new_rows = padded_elements.each_slice(14).map { |row| indent + ' ' + row.join(', ') } + # all lines but the last need a trailing comma + replace[arr, "[\n#{new_rows.join(",\n")}\n#{indent}]"] + } + else + raise "unknown #{exercise} #{key} #{format_type}" + end + } + File.write(filename, contents) +} From c6707c4d533ba9ff6bf15e60f6eae02608630401 Mon Sep 17 00:00:00 2001 From: Peter Tseng Date: Sat, 5 Mar 2022 02:45:05 +0000 Subject: [PATCH 3/7] add format-array to CI --- .github/workflows/action-format.yml | 2 +- .github/workflows/ci.yml | 8 +++++++- package.json | 1 - 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/.github/workflows/action-format.yml b/.github/workflows/action-format.yml index adc2396e45..ad68eaffc7 100644 --- a/.github/workflows/action-format.yml +++ b/.github/workflows/action-format.yml @@ -83,7 +83,7 @@ jobs: run: yarn install --pure-lockfile - name: Format JSON files - run: yarn format-json + run: yarn format-json && ruby bin/format-array.rb - name: 'Commit formatted code' run: | diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a20d8ff07d..494e396366 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -153,4 +153,10 @@ jobs: run: yarn install - name: Verify that json files are formatted correctly - run: yarn test-json-formatting + run: | + yarn format-json && ruby bin/format-array.rb + if ! git diff --quiet --exit-code; then + echo "please format the files with prettier and bin/format-array.rb, or apply the following changes:" + git diff + exit 1 + fi diff --git a/package.json b/package.json index ed217f9067..f6049cb0f6 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,6 @@ "scripts": { "test": "ajv -s canonical-data.schema.json -d \"exercises/*/canonical-data.json\"", "test-one": "ajv -s canonical-data.schema.json -d", - "test-json-formatting": "prettier --check **/*.json", "format-json": "prettier --write **/*.json" }, "resolutions": { From 0055ec1b02301b6d151dabd2d3d267847d82aed6 Mon Sep 17 00:00:00 2001 From: Peter Tseng Date: Sat, 5 Mar 2022 02:52:29 +0000 Subject: [PATCH 4/7] rm prettierignore with format-array running, we are now ready to allow prettier to format all. --- .prettierignore | 18 ------------------ 1 file changed, 18 deletions(-) delete mode 100644 .prettierignore diff --git a/.prettierignore b/.prettierignore deleted file mode 100644 index 2d1a836a16..0000000000 --- a/.prettierignore +++ /dev/null @@ -1,18 +0,0 @@ -exercises/book-store/canonical-data.json -exercises/bowling/canonical-data.json -exercises/change/canonical-data.json -exercises/connect/canonical-data.json -exercises/diamond/canonical-data.json -exercises/dominoes/canonical-data.json -exercises/flatten-array/canonical-data.json -exercises/forth/canonical-data.json -exercises/go-counting/canonical-data.json -exercises/minesweeper/canonical-data.json -exercises/ocr-numbers/canonical-data.json -exercises/rectangles/canonical-data.json -exercises/saddle-points/canonical-data.json -exercises/scale-generator/canonical-data.json -exercises/sieve/canonical-data.json -exercises/transpose/canonical-data.json -exercises/variable-length-quantity/canonical-data.json -exercises/word-search/canonical-data.json From ab562430972377b55a43e4ffbd44167968509b53 Mon Sep 17 00:00:00 2001 From: Peter Tseng Date: Sat, 5 Mar 2022 02:54:28 +0000 Subject: [PATCH 5/7] CONTRIBUTING: mention format-array --- CONTRIBUTING.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 300a498cd3..86b3fe317f 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -304,12 +304,12 @@ We are maintaining this section, since many open issues link to it. ## Formatting -This repository uses [prettier][prettier] to automatically format its JSON files. +This repository uses [prettier][prettier] and a [custom array formatter][format-array] to automatically format its JSON files. If you've added or modified a JSON file, you can format it using: ```shell yarn install -yarn format-json +yarn format-json && ruby bin/format-array.rb ``` Note: if you use VS Code as your editor, you can install the [prettier plugin][prettier-vs-code] to automatically handle formatting for you. @@ -336,4 +336,5 @@ Note: if you use VS Code as your editor, you can install the [prettier plugin][p [improve-exercise-metadata]: https://github.com/exercism/legacy-docs/blob/main/you-can-help/improve-exercise-metadata.md [legacy-docs]: https://github.com/exercism/legacy-docs [prettier]: https://prettier.io/ +[format-array]: https://github.com/exercism/problem-specifications/blob/main/bin/format-array.rb [prettier-vs-code]: https://marketplace.visualstudio.com/items?itemName=esbenp.prettier-vscode From 77536c71adfb0dd707fc67f163fe2a2197848693 Mon Sep 17 00:00:00 2001 From: Peter Tseng Date: Sat, 5 Mar 2022 02:46:09 +0000 Subject: [PATCH 6/7] do not merge: book-store: violate format-array --- exercises/book-store/canonical-data.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/exercises/book-store/canonical-data.json b/exercises/book-store/canonical-data.json index 17211d602c..d1a2619cd7 100644 --- a/exercises/book-store/canonical-data.json +++ b/exercises/book-store/canonical-data.json @@ -193,7 +193,9 @@ ], "property": "total", "input": { - "basket": [1, 2, 2, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 5] + "basket": [ + 1, 2, 2, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 5 + ] }, "expected": 10000 } From e1b5e85f1207f4a9fe854840dfa3425d79d807bd Mon Sep 17 00:00:00 2001 From: Peter Tseng Date: Sat, 5 Mar 2022 02:46:53 +0000 Subject: [PATCH 7/7] do not merge: accumulate: violate prettier --- exercises/accumulate/canonical-data.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/exercises/accumulate/canonical-data.json b/exercises/accumulate/canonical-data.json index 9df37968bf..dbaec1ee41 100644 --- a/exercises/accumulate/canonical-data.json +++ b/exercises/accumulate/canonical-data.json @@ -11,7 +11,7 @@ { "uuid": "64d97c14-36dd-44a8-9621-2cecebd6ed23", "description": "accumulate empty", - "property": "accumulate", + "property": "accumulate", "input": { "list": [], "accumulator": "(x) => x * x"