ruby-lsp-view_component 0.1.0 → 0.2.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 +4 -4
- data/CHANGELOG.md +4 -0
- data/README.md +21 -8
- data/lib/ruby_lsp/ruby_lsp_view_component/addon.rb +5 -2
- data/lib/ruby_lsp/ruby_lsp_view_component/code_lens.rb +53 -12
- data/lib/ruby_lsp_view_component/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: b6a116e228f6b21375f00e88b1d1f00f11013c9f132e381edc5a08461efd74f5
|
|
4
|
+
data.tar.gz: 523da12a2ef3e17ba2b72c61218cb87c97af8e1c3440a0a7612e39aa14ff2a47
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 642cef99fc71c8d9536c83f9f6bd17f88a5c83d36e1dc08f73b768ac7b38c61df40b8216cf35f5e6b43c9d3740f81d5d189f7cb4470d8ca21e00f132a3438e3b
|
|
7
|
+
data.tar.gz: 0a52380b179d23b4f6cd41de1873533990085aa6538761ca90752dab8605aa6b948c46eb35d71ec36016740fa242954f34bc49fa28e343bc80bcb5e75490f5ae
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
## [Unreleased]
|
|
2
2
|
|
|
3
|
+
## [0.2.0]
|
|
4
|
+
|
|
5
|
+
- `Jump to template` also shows on components that need a template and have none. It opens the missing `.html.erb` next to the class, and VS Code offers to create it.
|
|
6
|
+
|
|
3
7
|
## [0.1.0]
|
|
4
8
|
|
|
5
9
|
- `Jump to template` CodeLens on ViewComponent classes. It opens the component template, or a picker when the component has variants or sub-templates.
|
data/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# ruby-lsp-view_component
|
|
2
2
|
|
|
3
|
-
A [Ruby LSP](https://github.com/Shopify/ruby-lsp) add-on for [ViewComponent](https://viewcomponent.org). It adds a `Jump to template` CodeLens to each component class that opens the component's template.
|
|
3
|
+
A [Ruby LSP](https://github.com/Shopify/ruby-lsp) add-on for [ViewComponent](https://viewcomponent.org). It adds a `Jump to template` CodeLens to each component class that opens the component's template, or lets you create it when it is missing.
|
|
4
4
|
|
|
5
5
|
## Features
|
|
6
6
|
|
|
@@ -14,18 +14,31 @@ When the component has more than one template, the click opens a picker with the
|
|
|
14
14
|
|
|
15
15
|

|
|
16
16
|
|
|
17
|
-
For `app/components/
|
|
17
|
+
For `app/components/foo_component.rb` with all of these templates, the lens lists:
|
|
18
18
|
|
|
19
19
|
| File | What it is |
|
|
20
20
|
|---|---|
|
|
21
|
-
| `
|
|
22
|
-
| `
|
|
23
|
-
| `
|
|
24
|
-
| `
|
|
21
|
+
| `foo_component.html.erb` | [Main template](https://viewcomponent.org/guide/templates.html#sibling-file) |
|
|
22
|
+
| `foo_component.html+phone.erb` | Variant of the main template |
|
|
23
|
+
| `foo_component/foo_component.html.erb` | [Main template in a subdirectory](https://viewcomponent.org/guide/templates.html#subdirectory) |
|
|
24
|
+
| `foo_component/list.html.erb` | Sub-template rendered with `call_list`, from [view_component_subtemplates](https://github.com/bukhr/view_component_subtemplates) |
|
|
25
25
|
|
|
26
|
-
ERB, Haml and Slim templates are supported. Templates of nested components in the subdirectory, such as `
|
|
26
|
+
ERB, Haml and Slim templates are supported. Templates of nested components in the subdirectory, such as `foo_component/item_component.html.erb` next to `foo_component/item_component.rb`, belong to that component and are left out.
|
|
27
27
|
|
|
28
|
-
|
|
28
|
+
### Missing template
|
|
29
|
+
|
|
30
|
+
When a component needs a template and has none, the lens opens `foo_component.html.erb` next to the class. VS Code reports that the file does not exist and offers a **Create File** button. Once the file exists, the same lens opens it.
|
|
31
|
+
|
|
32
|
+

|
|
33
|
+
|
|
34
|
+
A component counts as needing a template only when it renders through a template file. There is no lens for components that:
|
|
35
|
+
|
|
36
|
+
- define [`call`](https://viewcomponent.org/guide/templates.html#call), themselves or in a parent class
|
|
37
|
+
- declare an [inline template](https://viewcomponent.org/guide/templates.html#inline) with `erb_template`, `haml_template` or `slim_template`
|
|
38
|
+
- [inherit](https://viewcomponent.org/guide/templates.html#inherited) a template from a parent class
|
|
39
|
+
- are marked `abstract!`
|
|
40
|
+
|
|
41
|
+
A base class that is never rendered on its own still gets the lens. Mark it `abstract!` to hide it.
|
|
29
42
|
|
|
30
43
|
## Installation
|
|
31
44
|
|
|
@@ -11,7 +11,9 @@ module RubyLsp
|
|
|
11
11
|
class Addon < ::RubyLsp::Addon
|
|
12
12
|
# @override
|
|
13
13
|
#: (GlobalState, Thread::Queue) -> void
|
|
14
|
-
def activate(global_state, outgoing_queue)
|
|
14
|
+
def activate(global_state, outgoing_queue)
|
|
15
|
+
@index = global_state.index #: RubyIndexer::Index?
|
|
16
|
+
end
|
|
15
17
|
|
|
16
18
|
# @override
|
|
17
19
|
#: -> void
|
|
@@ -32,7 +34,8 @@ module RubyLsp
|
|
|
32
34
|
# @override
|
|
33
35
|
#: (ResponseBuilders::CollectionResponseBuilder[Interface::CodeLens], URI::Generic, Prism::Dispatcher) -> void
|
|
34
36
|
def create_code_lens_listener(response_builder, uri, dispatcher)
|
|
35
|
-
|
|
37
|
+
index = @index #: as !nil
|
|
38
|
+
CodeLens.new(response_builder, uri, dispatcher, index)
|
|
36
39
|
end
|
|
37
40
|
end
|
|
38
41
|
end
|
|
@@ -6,37 +6,78 @@ module RubyLsp
|
|
|
6
6
|
include Requests::Support::Common
|
|
7
7
|
|
|
8
8
|
TEMPLATE_EXTENSIONS = "{erb,haml,slim}"
|
|
9
|
+
NO_TEMPLATE_CALLS = [:abstract!, :erb_template, :haml_template, :slim_template].freeze
|
|
9
10
|
|
|
10
|
-
#: (ResponseBuilders::CollectionResponseBuilder[Interface::CodeLens], URI::Generic, Prism::Dispatcher) -> void
|
|
11
|
-
def initialize(response_builder, uri, dispatcher)
|
|
11
|
+
#: (ResponseBuilders::CollectionResponseBuilder[Interface::CodeLens], URI::Generic, Prism::Dispatcher, RubyIndexer::Index) -> void
|
|
12
|
+
def initialize(response_builder, uri, dispatcher, index)
|
|
12
13
|
@response_builder = response_builder
|
|
14
|
+
@index = index
|
|
13
15
|
@path = uri.to_standardized_path #: String?
|
|
14
|
-
@
|
|
15
|
-
|
|
16
|
+
@nesting = [] #: Array[String]
|
|
17
|
+
@class_node = nil #: Prism::ClassNode?
|
|
18
|
+
return unless @path&.end_with?(".rb")
|
|
19
|
+
|
|
20
|
+
dispatcher.register(self, :on_module_node_enter, :on_module_node_leave, :on_class_node_enter, :on_class_node_leave)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
#: (Prism::ModuleNode) -> void
|
|
24
|
+
def on_module_node_enter(node)
|
|
25
|
+
@nesting << node.constant_path.slice
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
#: (Prism::ModuleNode) -> void
|
|
29
|
+
def on_module_node_leave(node)
|
|
30
|
+
@nesting.pop
|
|
16
31
|
end
|
|
17
32
|
|
|
18
33
|
#: (Prism::ClassNode) -> void
|
|
19
34
|
def on_class_node_enter(node)
|
|
20
|
-
|
|
35
|
+
@class_node ||= node
|
|
36
|
+
end
|
|
21
37
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
return
|
|
38
|
+
#: (Prism::ClassNode) -> void
|
|
39
|
+
def on_class_node_leave(node)
|
|
40
|
+
return unless node.equal?(@class_node)
|
|
41
|
+
|
|
42
|
+
path = @path #: as !nil
|
|
43
|
+
paths = templates(path)
|
|
44
|
+
paths = ["#{path.delete_suffix(".rb")}.html.erb"] if paths.empty? && needs_template?(node)
|
|
45
|
+
return if paths.empty?
|
|
25
46
|
|
|
26
47
|
@response_builder << create_code_lens(
|
|
27
48
|
node,
|
|
28
49
|
title: "Jump to template",
|
|
29
50
|
command_name: "rubyLsp.openFile",
|
|
30
|
-
arguments: [
|
|
51
|
+
arguments: [paths.map { |path| URI::Generic.from_path(path: path).to_s }],
|
|
31
52
|
data: { type: "file" },
|
|
32
53
|
)
|
|
33
54
|
end
|
|
34
55
|
|
|
35
56
|
private
|
|
36
57
|
|
|
37
|
-
#: ->
|
|
38
|
-
def
|
|
39
|
-
|
|
58
|
+
#: (Prism::ClassNode) -> bool
|
|
59
|
+
def needs_template?(node)
|
|
60
|
+
name = [*@nesting, node.constant_path.slice].join("::").delete_prefix("::")
|
|
61
|
+
ancestors = @index.linearized_ancestors_of(name)
|
|
62
|
+
base = ancestors.index("ViewComponent::Base")
|
|
63
|
+
return false unless base
|
|
64
|
+
return false if @index.resolve_method("call", name)
|
|
65
|
+
return false if (ancestors.take(base) - [name]).any? { |ancestor| inherited_template?(ancestor) }
|
|
66
|
+
|
|
67
|
+
Array(node.body&.child_nodes).none? do |child|
|
|
68
|
+
child.is_a?(Prism::CallNode) && child.receiver.nil? && NO_TEMPLATE_CALLS.include?(child.name)
|
|
69
|
+
end
|
|
70
|
+
rescue RubyIndexer::Index::NonExistingNamespaceError
|
|
71
|
+
false
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
#: (String) -> bool
|
|
75
|
+
def inherited_template?(ancestor)
|
|
76
|
+
@index[ancestor]&.any? { |entry| entry.file_path && templates(entry.file_path).any? } || false
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
#: (String) -> Array[String]
|
|
80
|
+
def templates(path)
|
|
40
81
|
dir = File.dirname(path)
|
|
41
82
|
name = File.basename(path, ".rb")
|
|
42
83
|
Dir.glob(["#{name}.*#{TEMPLATE_EXTENSIONS}", "#{name}/*.#{TEMPLATE_EXTENSIONS}"], base: dir)
|