w_syntax_tree-erb 0.9.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.github/ISSUE_TEMPLATE/formatting-report.md +37 -0
- data/.github/ISSUE_TEMPLATE/general.md +10 -0
- data/.github/dependabot.yml +6 -0
- data/.github/workflows/auto-merge.yml +22 -0
- data/.github/workflows/main.yml +32 -0
- data/.gitignore +10 -0
- data/CHANGELOG.md +16 -0
- data/Gemfile +5 -0
- data/Gemfile.lock +38 -0
- data/LICENSE +21 -0
- data/README.md +84 -0
- data/Rakefile +16 -0
- data/check_erb_parse.rb +20 -0
- data/lib/syntax_tree/erb/format.rb +230 -0
- data/lib/syntax_tree/erb/nodes.rb +511 -0
- data/lib/syntax_tree/erb/parser.rb +741 -0
- data/lib/syntax_tree/erb/pretty_print.rb +214 -0
- data/lib/syntax_tree/erb/version.rb +7 -0
- data/lib/syntax_tree/erb/visitor.rb +54 -0
- data/lib/syntax_tree/erb.rb +30 -0
- data/syntax_tree-erb.gemspec +33 -0
- metadata +151 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 4b1234ed5afef9a96ebefb415733354ac530ffa9bb35b718195d36be9f9fed7c
|
4
|
+
data.tar.gz: 03c589bb628753e874f05aa6ea504f0d3d14e2057a7843d919abfd31f9fc66e9
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: cb5dd5cad343aabf4eaa8bd8cb177eeabb673cf68b8a016f36c13ee5ff2fbbb277a313800849cee940013355e430edca26d5d8541d566588f7902c41816d20a9
|
7
|
+
data.tar.gz: 3a147354e9fbef1bf89b604033c57f3746f1e17e69d050e7b4b41ffc409005000f4a7e5a12085e2704da71c4de4e16691795f6fe4600ba04de1106f5179c15b6
|
@@ -0,0 +1,37 @@
|
|
1
|
+
---
|
2
|
+
name: Formatting report
|
3
|
+
about: Create a report to help us improve
|
4
|
+
title: "[Formatting] "
|
5
|
+
labels: bug
|
6
|
+
assignees: davidwessman
|
7
|
+
|
8
|
+
---
|
9
|
+
|
10
|
+
<details>
|
11
|
+
<summary>ERB-snippet</summary
|
12
|
+
|
13
|
+
```html
|
14
|
+
<% code %>
|
15
|
+
```
|
16
|
+
</details>
|
17
|
+
|
18
|
+
<details>
|
19
|
+
<summary>Expected formatting</summary
|
20
|
+
|
21
|
+
```html
|
22
|
+
<% code %>
|
23
|
+
```
|
24
|
+
</details>
|
25
|
+
<details>
|
26
|
+
<summary>Actual formatting</summary
|
27
|
+
|
28
|
+
```html
|
29
|
+
<% code %>
|
30
|
+
```
|
31
|
+
</details>
|
32
|
+
|
33
|
+
## Comment
|
34
|
+
|
35
|
+
## Versions
|
36
|
+
syntax_tree:
|
37
|
+
syntax_tree-erb:
|
@@ -0,0 +1,22 @@
|
|
1
|
+
name: Dependabot auto-merge
|
2
|
+
on: pull_request
|
3
|
+
|
4
|
+
permissions:
|
5
|
+
contents: write
|
6
|
+
pull-requests: write
|
7
|
+
|
8
|
+
jobs:
|
9
|
+
dependabot:
|
10
|
+
runs-on: ubuntu-latest
|
11
|
+
if: ${{ github.actor == 'dependabot[bot]' }}
|
12
|
+
steps:
|
13
|
+
- name: Dependabot metadata
|
14
|
+
id: metadata
|
15
|
+
uses: dependabot/fetch-metadata@v1.3.3
|
16
|
+
with:
|
17
|
+
github-token: "${{ secrets.GITHUB_TOKEN }}"
|
18
|
+
- name: Enable auto-merge for Dependabot PRs
|
19
|
+
run: gh pr merge --auto --merge "$PR_URL"
|
20
|
+
env:
|
21
|
+
PR_URL: ${{github.event.pull_request.html_url}}
|
22
|
+
GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}}
|
@@ -0,0 +1,32 @@
|
|
1
|
+
name: Main
|
2
|
+
on:
|
3
|
+
workflow_dispatch:
|
4
|
+
push:
|
5
|
+
branches:
|
6
|
+
- main
|
7
|
+
|
8
|
+
pull_request:
|
9
|
+
types: [opened, synchronize, reopened]
|
10
|
+
jobs:
|
11
|
+
ci:
|
12
|
+
strategy:
|
13
|
+
fail-fast: false
|
14
|
+
matrix:
|
15
|
+
ruby:
|
16
|
+
- "3.0"
|
17
|
+
- "3.1"
|
18
|
+
- "3.2"
|
19
|
+
name: CI
|
20
|
+
runs-on: ubuntu-latest
|
21
|
+
env:
|
22
|
+
CI: true
|
23
|
+
steps:
|
24
|
+
- uses: actions/checkout@master
|
25
|
+
- uses: ruby/setup-ruby@v1
|
26
|
+
with:
|
27
|
+
bundler-cache: true
|
28
|
+
ruby-version: ${{ matrix.ruby }}
|
29
|
+
- name: Test
|
30
|
+
run: |
|
31
|
+
bundle exec rake test
|
32
|
+
bundle exec rake stree:check
|
data/.gitignore
ADDED
data/CHANGELOG.md
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
# Changelog
|
2
|
+
|
3
|
+
All notable changes to this project will be documented in this file.
|
4
|
+
|
5
|
+
The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
|
6
|
+
|
7
|
+
## [Unreleased]
|
8
|
+
|
9
|
+
## [0.0.1] - 2023-05-25
|
10
|
+
|
11
|
+
### Added
|
12
|
+
|
13
|
+
- 🎉 First version based on syntax_tree-xml 🎉
|
14
|
+
|
15
|
+
[unreleased]: https://github.com/davidwessman/syntax_tree-erb/compare/v0.1.0...HEAD
|
16
|
+
[0.0.1]: https://github.com/davidwessman/syntax_tree-erb/compare/b280a...v0.1.0
|
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,38 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
syntax_tree-erb (0.0.1)
|
5
|
+
prettier_print (>= 1.2.0)
|
6
|
+
syntax_tree (>= 6.1.1)
|
7
|
+
|
8
|
+
GEM
|
9
|
+
remote: https://rubygems.org/
|
10
|
+
specs:
|
11
|
+
docile (1.4.0)
|
12
|
+
minitest (5.18.0)
|
13
|
+
prettier_print (1.2.1)
|
14
|
+
rake (13.0.6)
|
15
|
+
simplecov (0.22.0)
|
16
|
+
docile (~> 1.1)
|
17
|
+
simplecov-html (~> 0.11)
|
18
|
+
simplecov_json_formatter (~> 0.1)
|
19
|
+
simplecov-html (0.12.3)
|
20
|
+
simplecov_json_formatter (0.1.4)
|
21
|
+
syntax_tree (6.1.1)
|
22
|
+
prettier_print (>= 1.2.0)
|
23
|
+
|
24
|
+
PLATFORMS
|
25
|
+
arm64-darwin-21
|
26
|
+
x86_64-darwin-21
|
27
|
+
x86_64-darwin-22
|
28
|
+
x86_64-linux
|
29
|
+
|
30
|
+
DEPENDENCIES
|
31
|
+
bundler
|
32
|
+
minitest
|
33
|
+
rake
|
34
|
+
simplecov
|
35
|
+
syntax_tree-erb!
|
36
|
+
|
37
|
+
BUNDLED WITH
|
38
|
+
2.4.1
|
data/LICENSE
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2022-present Kevin Newton
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,84 @@
|
|
1
|
+
# SyntaxTree::ERB
|
2
|
+
|
3
|
+
[![Build Status](https://github.com/davidwessman/syntax_tree-erb/actions/workflows/main.yml/badge.svg)](https://github.com/davidwessman/syntax_tree-erb/actions/workflows/main.yml)
|
4
|
+
|
5
|
+
[Syntax Tree](https://github.com/ruby-syntax-tree/syntax_tree) support for ERB.
|
6
|
+
|
7
|
+
Currently handles
|
8
|
+
|
9
|
+
- ERB
|
10
|
+
- Tags with and without output
|
11
|
+
- Tags inside strings
|
12
|
+
- `if`, `elsif`, `else` and `unless` statements
|
13
|
+
- blocks
|
14
|
+
- comments
|
15
|
+
- Formatting of the ruby-code is done by `syntax_tree`
|
16
|
+
- HTML
|
17
|
+
- Tags with attributes
|
18
|
+
- Tags with and without closing tags
|
19
|
+
- Comments
|
20
|
+
- Vue
|
21
|
+
- Attributes, events and slots using `:`, `@` and `#` respectively
|
22
|
+
- Text output
|
23
|
+
|
24
|
+
## Unhandled cases
|
25
|
+
|
26
|
+
- Please add to this pinned issue (https://github.com/davidwessman/syntax_tree-erb/issues/28) or create a separate issue if you encounter formatting or parsing errors.
|
27
|
+
|
28
|
+
## Installation
|
29
|
+
|
30
|
+
Add this line to your application's Gemfile:
|
31
|
+
|
32
|
+
```ruby
|
33
|
+
gem "w_syntax_tree-erb", "~> 0.9", require: false
|
34
|
+
```
|
35
|
+
|
36
|
+
## Usage
|
37
|
+
|
38
|
+
```sh
|
39
|
+
bundle exec stree --plugins=erb "./**/*.html.erb"
|
40
|
+
```
|
41
|
+
|
42
|
+
From code:
|
43
|
+
|
44
|
+
```ruby
|
45
|
+
require "syntax_tree/erb"
|
46
|
+
|
47
|
+
pp SyntaxTree::ERB.parse(source) # print out the AST
|
48
|
+
puts SyntaxTree::ERB.format(source) # format the AST
|
49
|
+
```
|
50
|
+
|
51
|
+
## List all parsing errors
|
52
|
+
|
53
|
+
In order to get a list of all parsing errors (which needs to be fixed before the formatting works), this script can be used:
|
54
|
+
|
55
|
+
```ruby
|
56
|
+
#!/bin/ruby
|
57
|
+
|
58
|
+
require "syntax_tree/erb"
|
59
|
+
|
60
|
+
failures = []
|
61
|
+
|
62
|
+
Dir
|
63
|
+
.glob("./app/**/*.html.erb")
|
64
|
+
.each do |file|
|
65
|
+
puts("Processing #{file}")
|
66
|
+
begin
|
67
|
+
source = SyntaxTree::ERB.read(file)
|
68
|
+
SyntaxTree::ERB.parse(source)
|
69
|
+
SyntaxTree::ERB.format(source)
|
70
|
+
rescue => exception
|
71
|
+
failures << { file: file, message: exception.message }
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
puts failures
|
76
|
+
```
|
77
|
+
|
78
|
+
## Contributing
|
79
|
+
|
80
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/davidwessman/syntax_tree-erb.
|
81
|
+
|
82
|
+
## License
|
83
|
+
|
84
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
data/Rakefile
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "bundler/gem_tasks"
|
4
|
+
require "rake/testtask"
|
5
|
+
require "syntax_tree/rake_tasks"
|
6
|
+
|
7
|
+
Rake::TestTask.new(:test) do |t|
|
8
|
+
t.libs << "test"
|
9
|
+
t.libs << "lib"
|
10
|
+
t.test_files = FileList["test/**/*_test.rb"]
|
11
|
+
end
|
12
|
+
|
13
|
+
SyntaxTree::Rake::CheckTask.new
|
14
|
+
SyntaxTree::Rake::WriteTask.new
|
15
|
+
|
16
|
+
task default: :test
|
data/check_erb_parse.rb
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
#!/bin/ruby
|
2
|
+
|
3
|
+
require "syntax_tree/erb"
|
4
|
+
|
5
|
+
failures = []
|
6
|
+
|
7
|
+
Dir
|
8
|
+
.glob("./app/**/*.html.erb")
|
9
|
+
.each do |file|
|
10
|
+
puts("Processing #{file}")
|
11
|
+
begin
|
12
|
+
source = SyntaxTree::ERB.read(file)
|
13
|
+
SyntaxTree::ERB.parse(source)
|
14
|
+
SyntaxTree::ERB.format(source)
|
15
|
+
rescue => exception
|
16
|
+
failures << { file: file, message: exception.message }
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
puts failures
|
@@ -0,0 +1,230 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SyntaxTree
|
4
|
+
module ERB
|
5
|
+
class Format < Visitor
|
6
|
+
attr_reader :q
|
7
|
+
|
8
|
+
def initialize(q)
|
9
|
+
@q = q
|
10
|
+
end
|
11
|
+
|
12
|
+
# Visit a Token node.
|
13
|
+
def visit_token(node)
|
14
|
+
q.text(node.value.strip)
|
15
|
+
end
|
16
|
+
|
17
|
+
# Visit a Document node.
|
18
|
+
def visit_document(node)
|
19
|
+
child_nodes = node.child_nodes.sort_by(&:location)
|
20
|
+
|
21
|
+
q.seplist(child_nodes, -> { q.breakable(force: true) }) do |child_node|
|
22
|
+
visit(child_node)
|
23
|
+
end
|
24
|
+
|
25
|
+
q.breakable(force: true)
|
26
|
+
end
|
27
|
+
|
28
|
+
def visit_block(node)
|
29
|
+
visit(node.opening)
|
30
|
+
|
31
|
+
if node.elements.any?
|
32
|
+
q.indent do
|
33
|
+
q.breakable("")
|
34
|
+
q.seplist(
|
35
|
+
node.elements,
|
36
|
+
-> { q.breakable(force: true) }
|
37
|
+
) { |child_node| visit(child_node) }
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
if node.closing
|
42
|
+
q.breakable("")
|
43
|
+
visit(node.closing)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def visit_html(node)
|
48
|
+
visit_block(node)
|
49
|
+
end
|
50
|
+
|
51
|
+
def visit_erb_block(node)
|
52
|
+
visit_block(node)
|
53
|
+
end
|
54
|
+
|
55
|
+
def visit_erb_if(node)
|
56
|
+
visit_block(node)
|
57
|
+
end
|
58
|
+
|
59
|
+
def visit_erb_elsif(node)
|
60
|
+
visit_block(node)
|
61
|
+
end
|
62
|
+
|
63
|
+
def visit_erb_else(node)
|
64
|
+
visit_block(node)
|
65
|
+
end
|
66
|
+
|
67
|
+
def visit_erb_case(node)
|
68
|
+
visit_block(node)
|
69
|
+
end
|
70
|
+
|
71
|
+
def visit_erb_case_when(node)
|
72
|
+
visit_block(node)
|
73
|
+
end
|
74
|
+
|
75
|
+
# Visit an ErbNode node.
|
76
|
+
def visit_erb(node)
|
77
|
+
visit(node.opening_tag)
|
78
|
+
|
79
|
+
if node.keyword
|
80
|
+
q.text(" ")
|
81
|
+
visit(node.keyword)
|
82
|
+
end
|
83
|
+
|
84
|
+
visit(node.content)
|
85
|
+
|
86
|
+
visit(node.closing_tag)
|
87
|
+
end
|
88
|
+
|
89
|
+
def visit_erb_do_close(node)
|
90
|
+
visit(node.closing)
|
91
|
+
end
|
92
|
+
|
93
|
+
def visit_erb_close(node)
|
94
|
+
visit(node.closing)
|
95
|
+
end
|
96
|
+
|
97
|
+
# Visit an ErbEnd node.
|
98
|
+
def visit_erb_end(node)
|
99
|
+
visit(node.opening_tag)
|
100
|
+
q.text(" ")
|
101
|
+
visit(node.keyword)
|
102
|
+
q.text(" ")
|
103
|
+
visit(node.closing_tag)
|
104
|
+
end
|
105
|
+
|
106
|
+
def visit_erb_content(node)
|
107
|
+
rows =
|
108
|
+
if node.value.is_a?(String)
|
109
|
+
node.value.split("\n")
|
110
|
+
else
|
111
|
+
formatter =
|
112
|
+
SyntaxTree::Formatter.new("", [], erb_print_width(node.value))
|
113
|
+
formatter.format(node.value.statements)
|
114
|
+
formatter.flush
|
115
|
+
formatter.output.join.split("\n")
|
116
|
+
end
|
117
|
+
|
118
|
+
if rows.size > 1
|
119
|
+
q.group do
|
120
|
+
q.text(" ")
|
121
|
+
q.seplist(rows, -> { q.breakable(" ") }) { |row| q.text(row) }
|
122
|
+
q.text(" ")
|
123
|
+
end
|
124
|
+
elsif rows.size == 1
|
125
|
+
q.text(" ")
|
126
|
+
q.text(rows.first)
|
127
|
+
q.text(" ")
|
128
|
+
else
|
129
|
+
q.text(" ")
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
# Visit an HtmlNode::OpeningTag node.
|
134
|
+
def visit_opening_tag(node)
|
135
|
+
q.group do
|
136
|
+
visit(node.opening)
|
137
|
+
visit(node.name)
|
138
|
+
|
139
|
+
if node.attributes.any?
|
140
|
+
q.indent do
|
141
|
+
q.breakable
|
142
|
+
q.seplist(node.attributes, -> { q.breakable }) do |child_node|
|
143
|
+
visit(child_node)
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
q.breakable(node.closing.value == "/>" ? " " : "")
|
149
|
+
visit(node.closing)
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
# Visit an HtmlNode::ClosingTag node.
|
154
|
+
def visit_closing_tag(node)
|
155
|
+
q.group do
|
156
|
+
visit(node.opening)
|
157
|
+
visit(node.name)
|
158
|
+
visit(node.closing)
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
# Visit an Attribute node.
|
163
|
+
def visit_attribute(node)
|
164
|
+
q.group do
|
165
|
+
visit(node.key)
|
166
|
+
visit(node.equals)
|
167
|
+
visit(node.value)
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
171
|
+
# Visit a HtmlString node.
|
172
|
+
def visit_html_string(node)
|
173
|
+
q.group do
|
174
|
+
q.text("\"")
|
175
|
+
q.seplist(node.contents, -> { "" }) { |child_node| visit(child_node) }
|
176
|
+
q.text("\"")
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
def visit_html_comment(node)
|
181
|
+
visit(node.token)
|
182
|
+
end
|
183
|
+
|
184
|
+
# Visit a CharData node.
|
185
|
+
def visit_char_data(node)
|
186
|
+
lines = node.value.value.strip.split("\n")
|
187
|
+
|
188
|
+
if lines.size > 0
|
189
|
+
q.seplist(lines, -> { q.breakable(indent: false) }) do |line|
|
190
|
+
q.text(line)
|
191
|
+
end
|
192
|
+
end
|
193
|
+
end
|
194
|
+
|
195
|
+
# Visit a Doctype node.
|
196
|
+
def visit_doctype(node)
|
197
|
+
q.group do
|
198
|
+
visit(node.opening)
|
199
|
+
q.text(" ")
|
200
|
+
visit(node.name)
|
201
|
+
|
202
|
+
visit(node.closing)
|
203
|
+
end
|
204
|
+
end
|
205
|
+
|
206
|
+
def erb_print_width(syntax_tree)
|
207
|
+
statements = syntax_tree.statements.body
|
208
|
+
# Set the width to maximum if we have an IfNode or IfOp,
|
209
|
+
# we cannot format them purely with SyntaxTree because the ERB-syntax will be unparseable.
|
210
|
+
if statements.any? { |node| check_for_if_statement(node) }
|
211
|
+
999_999
|
212
|
+
else
|
213
|
+
SyntaxTree::ERB::MAX_WIDTH
|
214
|
+
end
|
215
|
+
end
|
216
|
+
|
217
|
+
def check_for_if_statement(node)
|
218
|
+
return false if node.nil?
|
219
|
+
|
220
|
+
if node.is_a?(SyntaxTree::IfNode) || node.is_a?(SyntaxTree::IfOp)
|
221
|
+
return true
|
222
|
+
end
|
223
|
+
|
224
|
+
node.child_nodes.any? do |child_node|
|
225
|
+
check_for_if_statement(child_node)
|
226
|
+
end
|
227
|
+
end
|
228
|
+
end
|
229
|
+
end
|
230
|
+
end
|