syntax_tree-tailwindcss 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.rubocop.yml +31 -0
- data/.streerc +1 -0
- data/LICENSE.txt +21 -0
- data/README.md +38 -0
- data/Rakefile +23 -0
- data/lib/syntax_tree/complete_mutation_visitor.rb +199 -0
- data/lib/syntax_tree/erb/mutation_visitor.rb +60 -0
- data/lib/syntax_tree/haml/mutation_visitor.rb +40 -0
- data/lib/syntax_tree/tailwindcss/erb_mutation_visitor.rb +57 -0
- data/lib/syntax_tree/tailwindcss/haml_mutation_visitor.rb +85 -0
- data/lib/syntax_tree/tailwindcss/patches.rb +59 -0
- data/lib/syntax_tree/tailwindcss/ruby_mutation_visitor.rb +83 -0
- data/lib/syntax_tree/tailwindcss/sorter.rb +65 -0
- data/lib/syntax_tree/tailwindcss/version.rb +7 -0
- data/lib/syntax_tree/tailwindcss.rb +32 -0
- metadata +75 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 4b9561bf73bff9b6e6a4c31fae7d4ff8d287625512a657baf29fd5ffe15a73cd
|
4
|
+
data.tar.gz: 3211cff998a98e08555fd66e19570e4f3ec5f0ce3064d33cd4088195adc11d3f
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: d1153927f9f76770ab2453c88761534d7c653df77afafd460e8db289734f04936c58b1406a0789b429428f3211b1e2bdd95ca89fbf167e3f4e7880b11610501e
|
7
|
+
data.tar.gz: 38ec5cf289bd018651f8f5efb64e0b0877edc193b9998d7bfba3d4024ff96b1132859207ead2f193874e13683ba6f5ebbdd921f8f7f0eb37193d07fb6f662915
|
data/.rubocop.yml
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
inherit_gem:
|
2
|
+
syntax_tree: config/rubocop.yml
|
3
|
+
|
4
|
+
AllCops:
|
5
|
+
DisplayCopNames: true
|
6
|
+
DisplayStyleGuide: true
|
7
|
+
NewCops: enable
|
8
|
+
SuggestExtensions: false
|
9
|
+
TargetRubyVersion: 3.0
|
10
|
+
|
11
|
+
Metrics:
|
12
|
+
Enabled: false
|
13
|
+
|
14
|
+
Style/Documentation:
|
15
|
+
Enabled: false
|
16
|
+
|
17
|
+
Naming/MethodName:
|
18
|
+
Enabled: false
|
19
|
+
|
20
|
+
Naming/MethodParameterName:
|
21
|
+
Enabled: false
|
22
|
+
|
23
|
+
Layout/LineLength:
|
24
|
+
Enabled: false
|
25
|
+
|
26
|
+
Style/GuardClause:
|
27
|
+
Enabled: false
|
28
|
+
|
29
|
+
Style/DocumentDynamicEvalDefinition:
|
30
|
+
Exclude:
|
31
|
+
- test/fixture/**/*.rb
|
data/.streerc
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--print-width=100
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2024 Tomasz Szczęśniak-Szlagowski
|
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,38 @@
|
|
1
|
+
# SyntaxTree plugin: tailwindcss
|
2
|
+
|
3
|
+
SyntaxTree can be used to format:
|
4
|
+
|
5
|
+
- `.rb` files that render HTML, e.g. Rails helpers
|
6
|
+
- `.haml` files (using the [haml](https://github.com/ruby-syntax-tree/syntax_tree-haml)
|
7
|
+
plugin)
|
8
|
+
- `.html.erb` files (using the [erb](https://github.com/davidwessman/syntax_tree-erb)
|
9
|
+
plugin)
|
10
|
+
|
11
|
+
All of these can contain TailwindCSS classes and this plugin will help you keep
|
12
|
+
them in order, just like
|
13
|
+
[the official prettier plugin](https://github.com/tailwindlabs/prettier-plugin-tailwindcss)
|
14
|
+
would.
|
15
|
+
|
16
|
+
## Installation
|
17
|
+
|
18
|
+
Install the gem and add to the application's Gemfile by executing:
|
19
|
+
|
20
|
+
$ bundle add syntax_tree-tailwindcss
|
21
|
+
|
22
|
+
If bundler is not being used to manage dependencies, install the gem by executing:
|
23
|
+
|
24
|
+
$ gem install syntax_tree-tailwindcss
|
25
|
+
|
26
|
+
## Usage
|
27
|
+
|
28
|
+
Example:
|
29
|
+
|
30
|
+
$ stree write --plugins=erb,tailwindcss app/helpers/**/*.rb app/views/**/*.html.erb
|
31
|
+
|
32
|
+
## Contributing
|
33
|
+
|
34
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/spect88/syntax_tree-tailwindcss.
|
35
|
+
|
36
|
+
## License
|
37
|
+
|
38
|
+
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,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "bundler/gem_tasks"
|
4
|
+
require "minitest/test_task"
|
5
|
+
require "syntax_tree/rake_tasks"
|
6
|
+
require "rubocop/rake_task"
|
7
|
+
|
8
|
+
Minitest::TestTask.create
|
9
|
+
|
10
|
+
stree_config =
|
11
|
+
proc { |t| t.source_files = FileList[%w[Gemfile Rakefile lib/**/*.rb test/**/*.rb *.gemspec]] }
|
12
|
+
SyntaxTree::Rake::CheckTask.new(&stree_config)
|
13
|
+
SyntaxTree::Rake::WriteTask.new(&stree_config)
|
14
|
+
|
15
|
+
RuboCop::RakeTask.new
|
16
|
+
|
17
|
+
desc "Run all code checks"
|
18
|
+
task check: %i[rubocop stree:check test]
|
19
|
+
|
20
|
+
desc "Run all automated fixes"
|
21
|
+
task fix: %i[stree:write rubocop:autocorrect_all]
|
22
|
+
|
23
|
+
task default: :check
|
@@ -0,0 +1,199 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SyntaxTree
|
4
|
+
# This is a fix for SyntaxTree::MutationVisitor not traversing the entire tree
|
5
|
+
class CompleteMutationVisitor < SyntaxTree::BasicVisitor
|
6
|
+
def initialize
|
7
|
+
super
|
8
|
+
@mutations = []
|
9
|
+
end
|
10
|
+
|
11
|
+
def mutate(query, &block)
|
12
|
+
@mutations << [Pattern.new(query).compile, block]
|
13
|
+
end
|
14
|
+
|
15
|
+
def visit(node)
|
16
|
+
return unless node
|
17
|
+
|
18
|
+
result = node.accept(self)
|
19
|
+
@mutations.each do |(pattern, mutation)|
|
20
|
+
result = mutation.call(result) if pattern.call(result)
|
21
|
+
end
|
22
|
+
|
23
|
+
result
|
24
|
+
end
|
25
|
+
|
26
|
+
def copy_and_visit_children(node)
|
27
|
+
node.dup.tap do |clone|
|
28
|
+
clone.instance_variables.each do |name|
|
29
|
+
next if %i[@location @comments].include?(name)
|
30
|
+
|
31
|
+
var = clone.instance_variable_get(name)
|
32
|
+
clone.instance_variable_set(name, visit_child(var))
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def visit_child(child)
|
38
|
+
case child
|
39
|
+
when Array
|
40
|
+
child.map { |node| visit_child(node) }
|
41
|
+
when SyntaxTree::Node
|
42
|
+
visit(child)
|
43
|
+
else
|
44
|
+
child
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
alias visit_aref copy_and_visit_children
|
49
|
+
alias visit_aref_field copy_and_visit_children
|
50
|
+
alias visit_alias copy_and_visit_children
|
51
|
+
alias visit_arg_block copy_and_visit_children
|
52
|
+
alias visit_arg_paren copy_and_visit_children
|
53
|
+
alias visit_arg_star copy_and_visit_children
|
54
|
+
alias visit_args copy_and_visit_children
|
55
|
+
alias visit_args_forward copy_and_visit_children
|
56
|
+
alias visit_array copy_and_visit_children
|
57
|
+
alias visit_aryptn copy_and_visit_children
|
58
|
+
alias visit_assign copy_and_visit_children
|
59
|
+
alias visit_assoc copy_and_visit_children
|
60
|
+
alias visit_assoc_splat copy_and_visit_children
|
61
|
+
alias visit_backref copy_and_visit_children
|
62
|
+
alias visit_backtick copy_and_visit_children
|
63
|
+
alias visit_bare_assoc_hash copy_and_visit_children
|
64
|
+
alias visit_BEGIN copy_and_visit_children
|
65
|
+
alias visit_begin copy_and_visit_children
|
66
|
+
alias visit_binary copy_and_visit_children
|
67
|
+
alias visit_block copy_and_visit_children
|
68
|
+
alias visit_blockarg copy_and_visit_children
|
69
|
+
alias visit_block_var copy_and_visit_children
|
70
|
+
alias visit_bodystmt copy_and_visit_children
|
71
|
+
alias visit_break copy_and_visit_children
|
72
|
+
alias visit_call copy_and_visit_children
|
73
|
+
alias visit_case copy_and_visit_children
|
74
|
+
alias visit_CHAR copy_and_visit_children
|
75
|
+
alias visit_class copy_and_visit_children
|
76
|
+
alias visit_comma copy_and_visit_children
|
77
|
+
alias visit_command copy_and_visit_children
|
78
|
+
alias visit_command_call copy_and_visit_children
|
79
|
+
alias visit_comment copy_and_visit_children
|
80
|
+
alias visit_const copy_and_visit_children
|
81
|
+
alias visit_const_path_field copy_and_visit_children
|
82
|
+
alias visit_const_path_ref copy_and_visit_children
|
83
|
+
alias visit_const_ref copy_and_visit_children
|
84
|
+
alias visit_cvar copy_and_visit_children
|
85
|
+
alias visit_def copy_and_visit_children
|
86
|
+
alias visit_defined copy_and_visit_children
|
87
|
+
alias visit_dyna_symbol copy_and_visit_children
|
88
|
+
alias visit_END copy_and_visit_children
|
89
|
+
alias visit_else copy_and_visit_children
|
90
|
+
alias visit_elsif copy_and_visit_children
|
91
|
+
alias visit_embdoc copy_and_visit_children
|
92
|
+
alias visit_embexpr_beg copy_and_visit_children
|
93
|
+
alias visit_embexpr_end copy_and_visit_children
|
94
|
+
alias visit_embvar copy_and_visit_children
|
95
|
+
alias visit_ensure copy_and_visit_children
|
96
|
+
alias visit_excessed_comma copy_and_visit_children
|
97
|
+
alias visit_field copy_and_visit_children
|
98
|
+
alias visit_float copy_and_visit_children
|
99
|
+
alias visit_fndptn copy_and_visit_children
|
100
|
+
alias visit_for copy_and_visit_children
|
101
|
+
alias visit_gvar copy_and_visit_children
|
102
|
+
alias visit_hash copy_and_visit_children
|
103
|
+
alias visit_heredoc copy_and_visit_children
|
104
|
+
alias visit_heredoc_beg copy_and_visit_children
|
105
|
+
alias visit_heredoc_end copy_and_visit_children
|
106
|
+
alias visit_hshptn copy_and_visit_children
|
107
|
+
alias visit_ident copy_and_visit_children
|
108
|
+
alias visit_if copy_and_visit_children
|
109
|
+
alias visit_if_op copy_and_visit_children
|
110
|
+
alias visit_imaginary copy_and_visit_children
|
111
|
+
alias visit_in copy_and_visit_children
|
112
|
+
alias visit_int copy_and_visit_children
|
113
|
+
alias visit_ivar copy_and_visit_children
|
114
|
+
alias visit_kw copy_and_visit_children
|
115
|
+
alias visit_kwrest_param copy_and_visit_children
|
116
|
+
alias visit_label copy_and_visit_children
|
117
|
+
alias visit_label_end copy_and_visit_children
|
118
|
+
alias visit_lambda copy_and_visit_children
|
119
|
+
alias visit_lambda_var copy_and_visit_children
|
120
|
+
alias visit_lbrace copy_and_visit_children
|
121
|
+
alias visit_lbracket copy_and_visit_children
|
122
|
+
alias visit_lparen copy_and_visit_children
|
123
|
+
alias visit_massign copy_and_visit_children
|
124
|
+
alias visit_method_add_block copy_and_visit_children
|
125
|
+
alias visit_mlhs copy_and_visit_children
|
126
|
+
alias visit_mlhs_paren copy_and_visit_children
|
127
|
+
alias visit_module copy_and_visit_children
|
128
|
+
alias visit_mrhs copy_and_visit_children
|
129
|
+
alias visit_next copy_and_visit_children
|
130
|
+
alias visit_not copy_and_visit_children
|
131
|
+
alias visit_op copy_and_visit_children
|
132
|
+
alias visit_opassign copy_and_visit_children
|
133
|
+
alias visit_params copy_and_visit_children
|
134
|
+
alias visit_paren copy_and_visit_children
|
135
|
+
alias visit_period copy_and_visit_children
|
136
|
+
alias visit_pinned_begin copy_and_visit_children
|
137
|
+
alias visit_pinned_var_ref copy_and_visit_children
|
138
|
+
alias visit_program copy_and_visit_children
|
139
|
+
alias visit_qsymbols copy_and_visit_children
|
140
|
+
alias visit_qsymbols_beg copy_and_visit_children
|
141
|
+
alias visit_qwords copy_and_visit_children
|
142
|
+
alias visit_qwords_beg copy_and_visit_children
|
143
|
+
alias visit_range copy_and_visit_children
|
144
|
+
alias visit_rassign copy_and_visit_children
|
145
|
+
alias visit_rational copy_and_visit_children
|
146
|
+
alias visit_rbrace copy_and_visit_children
|
147
|
+
alias visit_rbracket copy_and_visit_children
|
148
|
+
alias visit_redo copy_and_visit_children
|
149
|
+
alias visit_regexp_beg copy_and_visit_children
|
150
|
+
alias visit_regexp_content copy_and_visit_children
|
151
|
+
alias visit_regexp_end copy_and_visit_children
|
152
|
+
alias visit_regexp_literal copy_and_visit_children
|
153
|
+
alias visit_rescue copy_and_visit_children
|
154
|
+
alias visit_rescue_ex copy_and_visit_children
|
155
|
+
alias visit_rescue_mod copy_and_visit_children
|
156
|
+
alias visit_rest_param copy_and_visit_children
|
157
|
+
alias visit_retry copy_and_visit_children
|
158
|
+
alias visit_return copy_and_visit_children
|
159
|
+
alias visit_rparen copy_and_visit_children
|
160
|
+
alias visit_sclass copy_and_visit_children
|
161
|
+
alias visit_statements copy_and_visit_children
|
162
|
+
alias visit_string_concat copy_and_visit_children
|
163
|
+
alias visit_string_content copy_and_visit_children
|
164
|
+
alias visit_string_dvar copy_and_visit_children
|
165
|
+
alias visit_string_embexpr copy_and_visit_children
|
166
|
+
alias visit_string_literal copy_and_visit_children
|
167
|
+
alias visit_super copy_and_visit_children
|
168
|
+
alias visit_symbeg copy_and_visit_children
|
169
|
+
alias visit_symbol_content copy_and_visit_children
|
170
|
+
alias visit_symbol_literal copy_and_visit_children
|
171
|
+
alias visit_symbols copy_and_visit_children
|
172
|
+
alias visit_symbols_beg copy_and_visit_children
|
173
|
+
alias visit_tlambda copy_and_visit_children
|
174
|
+
alias visit_tlambeg copy_and_visit_children
|
175
|
+
alias visit_top_const_field copy_and_visit_children
|
176
|
+
alias visit_top_const_ref copy_and_visit_children
|
177
|
+
alias visit_tstring_beg copy_and_visit_children
|
178
|
+
alias visit_tstring_content copy_and_visit_children
|
179
|
+
alias visit_tstring_end copy_and_visit_children
|
180
|
+
alias visit_unary copy_and_visit_children
|
181
|
+
alias visit_undef copy_and_visit_children
|
182
|
+
alias visit_unless copy_and_visit_children
|
183
|
+
alias visit_until copy_and_visit_children
|
184
|
+
alias visit_var_field copy_and_visit_children
|
185
|
+
alias visit_var_ref copy_and_visit_children
|
186
|
+
alias visit_vcall copy_and_visit_children
|
187
|
+
alias visit_void_stmt copy_and_visit_children
|
188
|
+
alias visit_when copy_and_visit_children
|
189
|
+
alias visit_while copy_and_visit_children
|
190
|
+
alias visit_word copy_and_visit_children
|
191
|
+
alias visit_words copy_and_visit_children
|
192
|
+
alias visit_words_beg copy_and_visit_children
|
193
|
+
alias visit_xstring copy_and_visit_children
|
194
|
+
alias visit_xstring_literal copy_and_visit_children
|
195
|
+
alias visit_yield copy_and_visit_children
|
196
|
+
alias visit_zsuper copy_and_visit_children
|
197
|
+
alias visit___end__ copy_and_visit_children
|
198
|
+
end
|
199
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SyntaxTree
|
4
|
+
module ERB
|
5
|
+
# A base ERB Visitor class that returns a deep copy of the AST.
|
6
|
+
# It can be subclassed to mutate some parts of the AST and keep everything
|
7
|
+
# else as it was.
|
8
|
+
#
|
9
|
+
# Ideally this class (or equivalent) would exist within SyntaxTree::ERB gem.
|
10
|
+
class MutationVisitor
|
11
|
+
def visit(node)
|
12
|
+
node&.accept(self)
|
13
|
+
end
|
14
|
+
|
15
|
+
def copy_and_visit_children(node)
|
16
|
+
node.dup.tap do |clone|
|
17
|
+
clone.instance_variables.each do |name|
|
18
|
+
var = clone.instance_variable_get(name)
|
19
|
+
clone.instance_variable_set(
|
20
|
+
name,
|
21
|
+
case var
|
22
|
+
when Array
|
23
|
+
var.map { |child| visit(child) }
|
24
|
+
when SyntaxTree::ERB::Node
|
25
|
+
visit(var)
|
26
|
+
else
|
27
|
+
var
|
28
|
+
end
|
29
|
+
)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
alias visit_attribute copy_and_visit_children
|
35
|
+
alias visit_block copy_and_visit_children
|
36
|
+
alias visit_char_data copy_and_visit_children
|
37
|
+
alias visit_closing_tag copy_and_visit_children
|
38
|
+
alias visit_doctype copy_and_visit_children
|
39
|
+
alias visit_document copy_and_visit_children
|
40
|
+
alias visit_erb copy_and_visit_children
|
41
|
+
alias visit_erb_block copy_and_visit_children
|
42
|
+
alias visit_erb_case copy_and_visit_children
|
43
|
+
alias visit_erb_case_when copy_and_visit_children
|
44
|
+
alias visit_erb_close copy_and_visit_children
|
45
|
+
alias visit_erb_comment copy_and_visit_children
|
46
|
+
alias visit_erb_content copy_and_visit_children
|
47
|
+
alias visit_erb_do_close copy_and_visit_children
|
48
|
+
alias visit_erb_else copy_and_visit_children
|
49
|
+
alias visit_erb_end copy_and_visit_children
|
50
|
+
alias visit_erb_if copy_and_visit_children
|
51
|
+
alias visit_erb_yield copy_and_visit_children
|
52
|
+
alias visit_html copy_and_visit_children
|
53
|
+
alias visit_html_comment copy_and_visit_children
|
54
|
+
alias visit_html_string copy_and_visit_children
|
55
|
+
alias visit_new_line copy_and_visit_children
|
56
|
+
alias visit_opening_tag copy_and_visit_children
|
57
|
+
alias visit_token copy_and_visit_children
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SyntaxTree
|
4
|
+
module Haml
|
5
|
+
class MutationVisitor
|
6
|
+
# A base Haml Visitor class that returns a deep copy of the AST.
|
7
|
+
# It can be subclassed to mutate some parts of the AST and keep everything
|
8
|
+
# else as it was.
|
9
|
+
#
|
10
|
+
# Ideally this class (or equivalent) would existing within SyntaxTree::Haml gem.
|
11
|
+
def visit(node)
|
12
|
+
node&.accept(self)
|
13
|
+
end
|
14
|
+
|
15
|
+
def copy_and_visit_children(node)
|
16
|
+
# Haml::Parser::ParseNode has these main attributes
|
17
|
+
# - value: nil | Hash
|
18
|
+
# - children: [ParseNode]
|
19
|
+
# - parent: ParseNode
|
20
|
+
node.dup.tap do |clone|
|
21
|
+
clone.value = node.value.dup unless node.value.nil?
|
22
|
+
clone.children =
|
23
|
+
node.children.map do |child|
|
24
|
+
visit(child).tap { |child_copy| child_copy.parent = clone }
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
alias visit_comment copy_and_visit_children
|
30
|
+
alias visit_doctype copy_and_visit_children
|
31
|
+
alias visit_filter copy_and_visit_children
|
32
|
+
alias visit_haml_comment copy_and_visit_children
|
33
|
+
alias visit_plain copy_and_visit_children
|
34
|
+
alias visit_root copy_and_visit_children
|
35
|
+
alias visit_script copy_and_visit_children
|
36
|
+
alias visit_silent_script copy_and_visit_children
|
37
|
+
alias visit_tag copy_and_visit_children
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "syntax_tree/erb/mutation_visitor"
|
4
|
+
|
5
|
+
module SyntaxTree
|
6
|
+
module Tailwindcss
|
7
|
+
class ErbMutationVisitor < SyntaxTree::ERB::MutationVisitor
|
8
|
+
def initialize(sorter)
|
9
|
+
super()
|
10
|
+
@sorter = sorter
|
11
|
+
@ruby_visitor = SyntaxTree::Tailwindcss::RubyMutationVisitor.new(sorter)
|
12
|
+
end
|
13
|
+
|
14
|
+
# Rewrite `<%= tag.span class: 'foo bar' %>`
|
15
|
+
def visit_erb_content(node)
|
16
|
+
node.dup.tap do |clone|
|
17
|
+
clone.instance_variable_set(:@value, clone.value.accept(@ruby_visitor))
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
# Rewrite `class="foo <%= something %> bar"`
|
22
|
+
def visit_attribute(node)
|
23
|
+
clone = super
|
24
|
+
return clone unless node.key.value == "class"
|
25
|
+
return clone unless node.value.is_a?(SyntaxTree::ERB::HtmlString)
|
26
|
+
|
27
|
+
contents =
|
28
|
+
clone.value.contents.reject do |content|
|
29
|
+
content.is_a?(SyntaxTree::ERB::Token) && content.type == :whitespace
|
30
|
+
end
|
31
|
+
|
32
|
+
clone.value.instance_variable_set(
|
33
|
+
:@contents,
|
34
|
+
contents.map.with_index do |content, index|
|
35
|
+
next content unless content.is_a?(SyntaxTree::ERB::Token)
|
36
|
+
|
37
|
+
# It's already a clone, so it's OK to mutate it
|
38
|
+
new_value = @sorter.sort(content.value.split).join(" ")
|
39
|
+
new_value = " #{new_value}" if index.positive? && erb_node?(contents[index - 1])
|
40
|
+
new_value = "#{new_value} " if index < contents.size - 1 &&
|
41
|
+
erb_node?(contents[index + 1])
|
42
|
+
content.instance_variable_set(:@value, new_value)
|
43
|
+
content
|
44
|
+
end
|
45
|
+
)
|
46
|
+
|
47
|
+
clone
|
48
|
+
end
|
49
|
+
|
50
|
+
private
|
51
|
+
|
52
|
+
def erb_node?(node)
|
53
|
+
node.is_a?(SyntaxTree::ERB::ErbNode)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,85 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "syntax_tree/haml/mutation_visitor"
|
4
|
+
|
5
|
+
module SyntaxTree
|
6
|
+
module Tailwindcss
|
7
|
+
class HamlMutationVisitor < SyntaxTree::Haml::MutationVisitor
|
8
|
+
def initialize(sorter)
|
9
|
+
super()
|
10
|
+
@sorter = sorter
|
11
|
+
@ruby_visitor = SyntaxTree::Tailwindcss::RubyMutationVisitor.new(@sorter)
|
12
|
+
end
|
13
|
+
|
14
|
+
def visit_tag(node)
|
15
|
+
super.tap do |clone|
|
16
|
+
next unless node.value
|
17
|
+
|
18
|
+
# Rewrite `.foo.bar`
|
19
|
+
if node.value[:attributes].key?("class")
|
20
|
+
clone.value[:attributes]["class"] = @sorter.sort(
|
21
|
+
node.value[:attributes]["class"].split
|
22
|
+
).join(" ")
|
23
|
+
end
|
24
|
+
|
25
|
+
# Rewrite `(class="foo #{something} bar")`
|
26
|
+
if node.value[:dynamic_attributes].new&.include?('"class"')
|
27
|
+
clone.value[:dynamic_attributes] = node.value[:dynamic_attributes].dup.tap do |attrs|
|
28
|
+
attrs.new = rewrite_html_attributes(attrs.new)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
# Rewrites `(class="foo #{something} bar")`
|
35
|
+
def rewrite_html_attributes(string)
|
36
|
+
new_string = string.dup
|
37
|
+
|
38
|
+
pattern = <<~PATTERN
|
39
|
+
Assoc[
|
40
|
+
key: StringLiteral[parts: [TStringContent[value: "class"]]],
|
41
|
+
value: StringLiteral
|
42
|
+
]
|
43
|
+
PATTERN
|
44
|
+
|
45
|
+
SyntaxTree.search(string, pattern) do |assoc_node|
|
46
|
+
location = assoc_node.value.location
|
47
|
+
rewritten = @ruby_visitor.rewrite_string_literal(assoc_node.value)
|
48
|
+
|
49
|
+
formatter = SyntaxTree::Formatter.new(string, [], 10_000)
|
50
|
+
rewritten.format(formatter)
|
51
|
+
formatter.flush(0)
|
52
|
+
formatted = formatter.output.join
|
53
|
+
|
54
|
+
new_string[location.start_char...location.end_char] = formatted
|
55
|
+
end
|
56
|
+
|
57
|
+
new_string
|
58
|
+
end
|
59
|
+
|
60
|
+
def haml_attributes_mutation_visitor
|
61
|
+
@haml_attributes_mutation_visitor ||=
|
62
|
+
SyntaxTree.mutation do |visitor|
|
63
|
+
# Rewrite `class: ["foo", "bar"]` and `class: "foo bar"`
|
64
|
+
visitor.mutate(<<~PATTERN) do |node|
|
65
|
+
Assoc[
|
66
|
+
key: Label[value: 'class:'] | SymbolLiteral[value: Kw[value: 'class']],
|
67
|
+
value: ArrayLiteral[contents: Args] | StringLiteral
|
68
|
+
]
|
69
|
+
PATTERN
|
70
|
+
case node.value
|
71
|
+
when ArrayLiteral
|
72
|
+
args = @ruby_visitor.rewrite_args(node.value.contents)
|
73
|
+
node.copy(value: node.value.copy(contents: args))
|
74
|
+
when StringLiteral
|
75
|
+
new_value = @ruby_visitor.rewrite_string_literal(node.value)
|
76
|
+
node.copy(value: new_value)
|
77
|
+
else
|
78
|
+
node
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SyntaxTree
|
4
|
+
module Tailwindcss
|
5
|
+
module Patches
|
6
|
+
module SyntaxTree
|
7
|
+
def format_node(source, node, ...)
|
8
|
+
modified_node = node.accept(Tailwindcss.ruby_mutation_visitor)
|
9
|
+
super(source, modified_node, ...)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
module ERB
|
14
|
+
def parse(source)
|
15
|
+
node = ::SyntaxTree::ERB::Parser.new(source).parse
|
16
|
+
node.accept(Tailwindcss.erb_mutation_visitor)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
module Haml
|
21
|
+
def parse(source)
|
22
|
+
node = ::Haml::Parser.new({}).call(source)
|
23
|
+
node.accept(Tailwindcss.haml_mutation_visitor)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
module HamlFormat
|
28
|
+
def parse_attributes_hash(source, node, ...)
|
29
|
+
modified_node =
|
30
|
+
node.accept(Tailwindcss.haml_mutation_visitor.haml_attributes_mutation_visitor)
|
31
|
+
super(source, modified_node, ...)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
class << self
|
38
|
+
prepend Tailwindcss::Patches::SyntaxTree
|
39
|
+
end
|
40
|
+
|
41
|
+
# This should only be done if ERB plugin is already loaded
|
42
|
+
if const_defined?("ERB")
|
43
|
+
module ERB
|
44
|
+
class << self
|
45
|
+
prepend Tailwindcss::Patches::ERB
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
# This should only be done if Haml plugin is already loaded
|
51
|
+
if const_defined?("Haml")
|
52
|
+
module Haml
|
53
|
+
class << self
|
54
|
+
prepend Tailwindcss::Patches::Haml
|
55
|
+
end
|
56
|
+
Format.prepend(Tailwindcss::Patches::HamlFormat)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,83 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "syntax_tree/complete_mutation_visitor"
|
4
|
+
|
5
|
+
module SyntaxTree
|
6
|
+
module Tailwindcss
|
7
|
+
class RubyMutationVisitor < SyntaxTree::CompleteMutationVisitor
|
8
|
+
def initialize(sorter)
|
9
|
+
super()
|
10
|
+
@sorter = sorter
|
11
|
+
|
12
|
+
# Rewrite `class: "foo bar"` and `:class => "foo bar"`
|
13
|
+
pattern = <<~PATTERN
|
14
|
+
Assoc[
|
15
|
+
key: Label[value: 'class:'] | SymbolLiteral[value: Kw[value: 'class']],
|
16
|
+
value: StringLiteral
|
17
|
+
]
|
18
|
+
PATTERN
|
19
|
+
mutate(pattern) { |node| node.copy(value: rewrite_string_literal(node.value)) }
|
20
|
+
|
21
|
+
# Rewrite `class_names("foo bar", "lorem ipsum")`
|
22
|
+
mutate("CallNode[message: Ident[value: 'class_names']]") do |node|
|
23
|
+
next node unless node.arguments.is_a?(ArgParen)
|
24
|
+
next node unless node.arguments.arguments.is_a?(Args)
|
25
|
+
|
26
|
+
args = node.arguments.arguments
|
27
|
+
node.copy(arguments: node.arguments.copy(arguments: rewrite_args(args)))
|
28
|
+
end
|
29
|
+
|
30
|
+
# Rewrite `class_names "foo bar", "lorem ipsum"`
|
31
|
+
mutate("Command[message: Ident[value: 'class_names']]") do |node|
|
32
|
+
next node unless node.arguments.is_a?(Args)
|
33
|
+
|
34
|
+
node.copy(arguments: rewrite_args(node.arguments))
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def rewrite_string_literal(string_literal)
|
39
|
+
return string_literal unless string_literal.is_a?(StringLiteral)
|
40
|
+
|
41
|
+
string_parts =
|
42
|
+
string_literal.parts.map do |string_part|
|
43
|
+
next string_part unless string_part.is_a?(TStringContent)
|
44
|
+
|
45
|
+
value = string_part.value
|
46
|
+
new_value = @sorter.sort(value.split).join(" ")
|
47
|
+
new_value = " #{new_value}" if value.match?(/\A\s/)
|
48
|
+
new_value = "#{new_value} " if value.match?(/\s\z/)
|
49
|
+
string_part.copy(value: new_value)
|
50
|
+
end
|
51
|
+
string_literal.copy(parts: string_parts)
|
52
|
+
end
|
53
|
+
|
54
|
+
def rewrite_args(args)
|
55
|
+
return args unless args.is_a?(Args)
|
56
|
+
|
57
|
+
# Rewrite each string literal separately, e.g. 'mb-4 text-2xl'
|
58
|
+
rewritten_parts = args.parts.map { |part| rewrite_string_literal(part) }
|
59
|
+
|
60
|
+
# Reorder subsequent single-class strings, e.g. 'mb-4', 'text-2xl'
|
61
|
+
reordered_parts =
|
62
|
+
rewritten_parts
|
63
|
+
.slice_when { |a, b| single_class_string_literal?(a) ^ single_class_string_literal?(b) }
|
64
|
+
.map do |segment|
|
65
|
+
next segment if segment.size == 1 || !single_class_string_literal?(segment.first)
|
66
|
+
|
67
|
+
segment
|
68
|
+
.uniq { |node| node.parts.first.value }
|
69
|
+
.sort_by { |node| @sorter.sort_order(node.parts.first.value) }
|
70
|
+
end
|
71
|
+
.flatten
|
72
|
+
|
73
|
+
args.copy(parts: reordered_parts)
|
74
|
+
end
|
75
|
+
|
76
|
+
def single_class_string_literal?(string_literal)
|
77
|
+
string_literal.is_a?(StringLiteral) && string_literal.parts.size == 1 &&
|
78
|
+
string_literal.parts.first.is_a?(TStringContent) &&
|
79
|
+
string_literal.parts.first.value.match?(/\A\S+\z/)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SyntaxTree
|
4
|
+
module Tailwindcss
|
5
|
+
class Sorter
|
6
|
+
def initialize(classes_in_order)
|
7
|
+
@classes_order = classes_in_order.to_enum.with_index.to_h
|
8
|
+
end
|
9
|
+
|
10
|
+
def sort(classes)
|
11
|
+
classes.uniq.sort_by { |cls| @classes_order[cls] || -1 }
|
12
|
+
end
|
13
|
+
|
14
|
+
def sort_order(cls)
|
15
|
+
@classes_order[cls] || -1
|
16
|
+
end
|
17
|
+
|
18
|
+
class << self
|
19
|
+
def load_cached
|
20
|
+
return new(SyntaxTree::Tailwindcss.custom_order) if SyntaxTree::Tailwindcss.custom_order
|
21
|
+
|
22
|
+
mtime = File.mtime(tailwind_output_path)
|
23
|
+
return @cached_sorter if @cached_mtime == mtime
|
24
|
+
|
25
|
+
@cached_sorter = load!
|
26
|
+
@cached_mtime = mtime
|
27
|
+
@cached_sorter
|
28
|
+
rescue Errno::ENOENT
|
29
|
+
warn do
|
30
|
+
"Couldn't find TailwindCSS output (#{tailwind_output_path}). Classes won't be sorted."
|
31
|
+
end
|
32
|
+
new([])
|
33
|
+
end
|
34
|
+
|
35
|
+
def load!
|
36
|
+
tailwind_output = File.read(tailwind_output_path)
|
37
|
+
classes = parse_tailwind_output(tailwind_output)
|
38
|
+
new(classes)
|
39
|
+
end
|
40
|
+
|
41
|
+
def tailwind_output_path
|
42
|
+
if SyntaxTree::Tailwindcss.output_path
|
43
|
+
SyntaxTree::Tailwindcss.output_path
|
44
|
+
elsif ENV["TAILWIND_OUTPUT_PATH"]
|
45
|
+
ENV["TAILWIND_OUTPUT_PATH"]
|
46
|
+
else
|
47
|
+
"app/assets/builds/application.css"
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def parse_tailwind_output(output)
|
52
|
+
# We could use a real CSS parser, but that'd be slow and we only need to know valid classes
|
53
|
+
output
|
54
|
+
.gsub(%r{/\*.+?\*/}, "")
|
55
|
+
.gsub(/\\(.)/) { "-ESCAPED-#{Regexp.last_match[1].ord}-" }
|
56
|
+
.scan(/(?<=\.)[^0-9][^. {:>),\[]*/)
|
57
|
+
.uniq
|
58
|
+
.join(" ")
|
59
|
+
.gsub(/-ESCAPED-(\d+)-/) { Regexp.last_match[1].to_i.chr(Encoding::UTF_8) }
|
60
|
+
.split
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "tailwindcss/version"
|
4
|
+
require_relative "tailwindcss/patches"
|
5
|
+
|
6
|
+
module SyntaxTree
|
7
|
+
module Tailwindcss
|
8
|
+
autoload :Sorter, "syntax_tree/tailwindcss/sorter"
|
9
|
+
autoload :RubyMutationVisitor, "syntax_tree/tailwindcss/ruby_mutation_visitor"
|
10
|
+
autoload :ErbMutationVisitor, "syntax_tree/tailwindcss/erb_mutation_visitor"
|
11
|
+
autoload :HamlMutationVisitor, "syntax_tree/tailwindcss/haml_mutation_visitor"
|
12
|
+
|
13
|
+
class << self
|
14
|
+
attr_accessor :output_path, :custom_order
|
15
|
+
|
16
|
+
def ruby_mutation_visitor
|
17
|
+
sorter = Sorter.load_cached
|
18
|
+
RubyMutationVisitor.new(sorter)
|
19
|
+
end
|
20
|
+
|
21
|
+
def erb_mutation_visitor
|
22
|
+
sorter = Sorter.load_cached
|
23
|
+
ErbMutationVisitor.new(sorter)
|
24
|
+
end
|
25
|
+
|
26
|
+
def haml_mutation_visitor
|
27
|
+
sorter = Sorter.load_cached
|
28
|
+
HamlMutationVisitor.new(sorter)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
metadata
ADDED
@@ -0,0 +1,75 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: syntax_tree-tailwindcss
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Tomasz Szczęśniak-Szlagowski
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2025-02-14 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: syntax_tree
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '6.2'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '6.2'
|
27
|
+
description: It sorts Tailwind classes in your helpers and ERB files and sorts them
|
28
|
+
email:
|
29
|
+
- spect88@gmail.com
|
30
|
+
executables: []
|
31
|
+
extensions: []
|
32
|
+
extra_rdoc_files: []
|
33
|
+
files:
|
34
|
+
- ".rubocop.yml"
|
35
|
+
- ".streerc"
|
36
|
+
- LICENSE.txt
|
37
|
+
- README.md
|
38
|
+
- Rakefile
|
39
|
+
- lib/syntax_tree/complete_mutation_visitor.rb
|
40
|
+
- lib/syntax_tree/erb/mutation_visitor.rb
|
41
|
+
- lib/syntax_tree/haml/mutation_visitor.rb
|
42
|
+
- lib/syntax_tree/tailwindcss.rb
|
43
|
+
- lib/syntax_tree/tailwindcss/erb_mutation_visitor.rb
|
44
|
+
- lib/syntax_tree/tailwindcss/haml_mutation_visitor.rb
|
45
|
+
- lib/syntax_tree/tailwindcss/patches.rb
|
46
|
+
- lib/syntax_tree/tailwindcss/ruby_mutation_visitor.rb
|
47
|
+
- lib/syntax_tree/tailwindcss/sorter.rb
|
48
|
+
- lib/syntax_tree/tailwindcss/version.rb
|
49
|
+
homepage: https://github.com/spect88/syntax_tree-tailwindcss
|
50
|
+
licenses:
|
51
|
+
- MIT
|
52
|
+
metadata:
|
53
|
+
homepage_uri: https://github.com/spect88/syntax_tree-tailwindcss
|
54
|
+
source_code_uri: https://github.com/spect88/syntax_tree-tailwindcss
|
55
|
+
rubygems_mfa_required: 'true'
|
56
|
+
post_install_message:
|
57
|
+
rdoc_options: []
|
58
|
+
require_paths:
|
59
|
+
- lib
|
60
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
61
|
+
requirements:
|
62
|
+
- - ">="
|
63
|
+
- !ruby/object:Gem::Version
|
64
|
+
version: 3.0.0
|
65
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
66
|
+
requirements:
|
67
|
+
- - ">="
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: '0'
|
70
|
+
requirements: []
|
71
|
+
rubygems_version: 3.3.7
|
72
|
+
signing_key:
|
73
|
+
specification_version: 4
|
74
|
+
summary: A Syntax Tree plugin for sorting TailwindCSS classes
|
75
|
+
test_files: []
|