js-routes 2.2.7 → 2.2.9
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +25 -9
- data/Gemfile +5 -0
- data/Readme.md +63 -47
- data/bin/tapioca +27 -0
- data/js-routes.gemspec +4 -1
- data/lib/js_routes/configuration.rb +80 -34
- data/lib/js_routes/engine.rb +2 -0
- data/lib/js_routes/generators/base.rb +15 -1
- data/lib/js_routes/generators/middleware.rb +3 -6
- data/lib/js_routes/generators/webpacker.rb +2 -4
- data/lib/js_routes/instance.rb +42 -15
- data/lib/js_routes/middleware.rb +14 -3
- data/lib/js_routes/route.rb +57 -16
- data/lib/js_routes/types.rb +28 -0
- data/lib/js_routes/version.rb +2 -1
- data/lib/js_routes.rb +20 -7
- data/lib/routes.js +25 -3
- data/lib/routes.ts +30 -6
- data/sorbet/config +4 -0
- data/sorbet/rbi/annotations/.gitattributes +1 -0
- data/sorbet/rbi/annotations/actionpack.rbi +428 -0
- data/sorbet/rbi/annotations/actionview.rbi +75 -0
- data/sorbet/rbi/annotations/activesupport.rbi +421 -0
- data/sorbet/rbi/annotations/railties.rbi +61 -0
- data/sorbet/rbi/gems/.gitattributes +1 -0
- data/sorbet/rbi/gems/actionpack@7.0.4.1.rbi +303 -0
- data/sorbet/rbi/gems/actionview@7.0.4.1.rbi +8 -0
- data/sorbet/rbi/gems/activesupport@7.0.4.1.rbi +16424 -0
- data/sorbet/rbi/gems/appraisal@2.4.1.rbi +584 -0
- data/sorbet/rbi/gems/builder@3.2.4.rbi +8 -0
- data/sorbet/rbi/gems/bump@0.10.0.rbi +168 -0
- data/sorbet/rbi/gems/byebug@11.1.3.rbi +3606 -0
- data/sorbet/rbi/gems/coderay@1.1.3.rbi +3426 -0
- data/sorbet/rbi/gems/concurrent-ruby@1.2.0.rbi +11570 -0
- data/sorbet/rbi/gems/crass@1.0.6.rbi +8 -0
- data/sorbet/rbi/gems/diff-lcs@1.5.0.rbi +1083 -0
- data/sorbet/rbi/gems/erubi@1.12.0.rbi +145 -0
- data/sorbet/rbi/gems/i18n@1.12.0.rbi +2296 -0
- data/sorbet/rbi/gems/libv8-node@16.10.0.0.rbi +8 -0
- data/sorbet/rbi/gems/loofah@2.19.1.rbi +8 -0
- data/sorbet/rbi/gems/method_source@1.0.0.rbi +272 -0
- data/sorbet/rbi/gems/mini_racer@0.6.3.rbi +224 -0
- data/sorbet/rbi/gems/minitest@5.17.0.rbi +1457 -0
- data/sorbet/rbi/gems/netrc@0.11.0.rbi +158 -0
- data/sorbet/rbi/gems/nokogiri@1.14.0.rbi +8 -0
- data/sorbet/rbi/gems/parallel@1.24.0.rbi +280 -0
- data/sorbet/rbi/gems/prettier_print@1.2.1.rbi +951 -0
- data/sorbet/rbi/gems/prism@0.24.0.rbi +29744 -0
- data/sorbet/rbi/gems/pry-byebug@3.10.1.rbi +1150 -0
- data/sorbet/rbi/gems/pry@0.14.2.rbi +10075 -0
- data/sorbet/rbi/gems/racc@1.6.2.rbi +150 -0
- data/sorbet/rbi/gems/rack-test@2.0.2.rbi +8 -0
- data/sorbet/rbi/gems/rack@2.2.6.2.rbi +5585 -0
- data/sorbet/rbi/gems/rails-dom-testing@2.0.3.rbi +8 -0
- data/sorbet/rbi/gems/rails-html-sanitizer@1.5.0.rbi +8 -0
- data/sorbet/rbi/gems/railties@7.0.4.1.rbi +1959 -0
- data/sorbet/rbi/gems/rake@13.0.6.rbi +3072 -0
- data/sorbet/rbi/gems/rbi@0.1.9.rbi +3006 -0
- data/sorbet/rbi/gems/rspec-core@3.12.0.rbi +10868 -0
- data/sorbet/rbi/gems/rspec-expectations@3.12.2.rbi +8100 -0
- data/sorbet/rbi/gems/rspec-mocks@3.12.3.rbi +5299 -0
- data/sorbet/rbi/gems/rspec-support@3.12.0.rbi +1611 -0
- data/sorbet/rbi/gems/rspec@3.12.0.rbi +82 -0
- data/sorbet/rbi/gems/spoom@1.2.4.rbi +3777 -0
- data/sorbet/rbi/gems/sprockets-rails@3.4.2.rbi +14 -0
- data/sorbet/rbi/gems/sprockets@4.2.0.rbi +8 -0
- data/sorbet/rbi/gems/syntax_tree@6.2.0.rbi +23136 -0
- data/sorbet/rbi/gems/tapioca@0.12.0.rbi +3510 -0
- data/sorbet/rbi/gems/thor@1.2.1.rbi +3956 -0
- data/sorbet/rbi/gems/tzinfo@2.0.5.rbi +5914 -0
- data/sorbet/rbi/gems/yard-sorbet@0.8.1.rbi +428 -0
- data/sorbet/rbi/gems/yard@0.9.34.rbi +18332 -0
- data/sorbet/rbi/gems/zeitwerk@2.6.8.rbi +8 -0
- data/sorbet/rbi/todo.rbi +31 -0
- data/sorbet/tapioca/config.yml +13 -0
- data/sorbet/tapioca/require.rb +4 -0
- data/spec/js_routes/default_serializer_spec.rb +1 -1
- data/spec/js_routes/module_types/dts_spec.rb +17 -2
- data/spec/js_routes/module_types/umd_spec.rb +2 -2
- data/spec/js_routes/options_spec.rb +6 -6
- data/spec/js_routes/rails_routes_compatibility_spec.rb +17 -12
- data/spec/js_routes/route_specification_spec.rb +1 -4
- data/spec/js_routes/{zzz_last_post_rails_init_spec.rb → zzz_sprockets_spec.rb} +1 -1
- data/spec/spec_helper.rb +4 -0
- metadata +82 -7
data/lib/js_routes/instance.rb
CHANGED
@@ -1,26 +1,35 @@
|
|
1
|
+
# typed: strict
|
1
2
|
require "js_routes/configuration"
|
2
3
|
require "js_routes/route"
|
4
|
+
require "js_routes/types"
|
3
5
|
|
4
6
|
module JsRoutes
|
5
7
|
class Instance # :nodoc:
|
8
|
+
include JsRoutes::Types
|
9
|
+
extend T::Sig
|
6
10
|
|
11
|
+
sig { returns(JsRoutes::Configuration) }
|
7
12
|
attr_reader :configuration
|
8
13
|
#
|
9
14
|
# Implementation
|
10
15
|
#
|
11
16
|
|
12
|
-
|
13
|
-
|
17
|
+
sig { params(options: T.untyped).void }
|
18
|
+
def initialize(**options)
|
19
|
+
options = T.let(options, Options)
|
20
|
+
@configuration = T.let(JsRoutes.configuration.merge(options), JsRoutes::Configuration)
|
14
21
|
end
|
15
22
|
|
23
|
+
sig {returns(String)}
|
16
24
|
def generate
|
17
25
|
# Ensure routes are loaded. If they're not, load them.
|
18
|
-
|
26
|
+
application = T.unsafe(self.application)
|
27
|
+
if named_routes.empty? && application.respond_to?(:reload_routes!, true)
|
19
28
|
application.reload_routes!
|
20
29
|
end
|
21
30
|
content = File.read(@configuration.source_file)
|
22
31
|
|
23
|
-
|
32
|
+
unless @configuration.dts?
|
24
33
|
content = js_variables.inject(content) do |js, (key, value)|
|
25
34
|
js.gsub!("RubyVariables.#{key}", value.to_s) ||
|
26
35
|
raise("Missing key #{key} in JS template")
|
@@ -29,11 +38,12 @@ module JsRoutes
|
|
29
38
|
content + routes_export + prevent_types_export
|
30
39
|
end
|
31
40
|
|
41
|
+
sig { void }
|
32
42
|
def generate!
|
33
43
|
# Some libraries like Devise did not load their routes yet
|
34
44
|
# so we will wait until initialization process finishes
|
35
45
|
# https://github.com/railsware/js-routes/issues/7
|
36
|
-
Rails.configuration.after_initialize do
|
46
|
+
T.unsafe(Rails).configuration.after_initialize do
|
37
47
|
file_path = Rails.root.join(@configuration.output_file)
|
38
48
|
source_code = generate
|
39
49
|
|
@@ -49,16 +59,20 @@ module JsRoutes
|
|
49
59
|
|
50
60
|
protected
|
51
61
|
|
62
|
+
sig { returns(T::Hash[String, String]) }
|
52
63
|
def js_variables
|
64
|
+
version = Rails.version
|
65
|
+
prefix = @configuration.prefix
|
66
|
+
prefix = prefix.call if prefix.is_a?(Proc)
|
53
67
|
{
|
54
68
|
'GEM_VERSION' => JsRoutes::VERSION,
|
55
69
|
'ROUTES_OBJECT' => routes_object,
|
56
|
-
'RAILS_VERSION' =>
|
57
|
-
'DEPRECATED_GLOBBING_BEHAVIOR' =>
|
58
|
-
'DEPRECATED_FALSE_PARAMETER_BEHAVIOR' =>
|
70
|
+
'RAILS_VERSION' => ::Rails.version,
|
71
|
+
'DEPRECATED_GLOBBING_BEHAVIOR' => version >= '4.0.0' && version < '4.1.0',
|
72
|
+
'DEPRECATED_FALSE_PARAMETER_BEHAVIOR' => version < '7.0.0',
|
59
73
|
'APP_CLASS' => application.class.to_s,
|
60
74
|
'DEFAULT_URL_OPTIONS' => json(@configuration.default_url_options),
|
61
|
-
'PREFIX' => json(
|
75
|
+
'PREFIX' => json(prefix),
|
62
76
|
'SPECIAL_OPTIONS_KEY' => json(@configuration.special_options_key),
|
63
77
|
'SERIALIZER' => @configuration.serializer || json(nil),
|
64
78
|
'MODULE_TYPE' => json(@configuration.module_type),
|
@@ -66,6 +80,7 @@ module JsRoutes
|
|
66
80
|
}
|
67
81
|
end
|
68
82
|
|
83
|
+
sig { returns(String) }
|
69
84
|
def wrapper_variable
|
70
85
|
case @configuration.module_type
|
71
86
|
when 'ESM'
|
@@ -86,18 +101,22 @@ module JsRoutes
|
|
86
101
|
end
|
87
102
|
end
|
88
103
|
|
104
|
+
sig { returns(Application) }
|
89
105
|
def application
|
90
|
-
@configuration.application
|
106
|
+
@configuration.application.call
|
91
107
|
end
|
92
108
|
|
93
|
-
|
94
|
-
|
109
|
+
sig { params(value: T.untyped).returns(String) }
|
110
|
+
def json(value)
|
111
|
+
JsRoutes.json(value)
|
95
112
|
end
|
96
113
|
|
114
|
+
sig { returns(T::Hash[Symbol, JourneyRoute]) }
|
97
115
|
def named_routes
|
98
|
-
application.routes.named_routes.
|
116
|
+
T.unsafe(application).routes.named_routes.to_h
|
99
117
|
end
|
100
118
|
|
119
|
+
sig { returns(String) }
|
101
120
|
def routes_object
|
102
121
|
return json({}) if @configuration.modern?
|
103
122
|
properties = routes_list.map do |comment, name, body|
|
@@ -106,10 +125,11 @@ module JsRoutes
|
|
106
125
|
"{\n" + properties.join(",\n\n") + "}\n"
|
107
126
|
end
|
108
127
|
|
128
|
+
sig { returns(T::Array[StringArray]) }
|
109
129
|
def static_exports
|
110
130
|
[:configure, :config, :serialize].map do |name|
|
111
131
|
[
|
112
|
-
"", name,
|
132
|
+
"", name.to_s,
|
113
133
|
@configuration.dts? ?
|
114
134
|
"RouterExposedMethods['#{name}']" :
|
115
135
|
"__jsr.#{name}"
|
@@ -117,6 +137,7 @@ module JsRoutes
|
|
117
137
|
end
|
118
138
|
end
|
119
139
|
|
140
|
+
sig { returns(String) }
|
120
141
|
def routes_export
|
121
142
|
return "" unless @configuration.modern?
|
122
143
|
[*static_exports, *routes_list].map do |comment, name, body|
|
@@ -124,6 +145,7 @@ module JsRoutes
|
|
124
145
|
end.join
|
125
146
|
end
|
126
147
|
|
148
|
+
sig { returns(String) }
|
127
149
|
def prevent_types_export
|
128
150
|
return "" unless @configuration.dts?
|
129
151
|
<<-JS
|
@@ -133,18 +155,21 @@ export {};
|
|
133
155
|
JS
|
134
156
|
end
|
135
157
|
|
158
|
+
sig { returns(String) }
|
136
159
|
def export_separator
|
137
160
|
@configuration.dts? ? ': ' : ' = '
|
138
161
|
end
|
139
162
|
|
163
|
+
sig { returns(T::Array[StringArray]) }
|
140
164
|
def routes_list
|
141
165
|
named_routes.sort_by(&:first).flat_map do |_, route|
|
142
166
|
route_helpers_if_match(route) + mounted_app_routes(route)
|
143
167
|
end
|
144
168
|
end
|
145
169
|
|
170
|
+
sig { params(route: JourneyRoute).returns(T::Array[StringArray]) }
|
146
171
|
def mounted_app_routes(route)
|
147
|
-
rails_engine_app = app_from_route(route)
|
172
|
+
rails_engine_app = T.unsafe(app_from_route(route))
|
148
173
|
if rails_engine_app.respond_to?(:superclass) &&
|
149
174
|
rails_engine_app.superclass == Rails::Engine && !route.path.anchored
|
150
175
|
rails_engine_app.routes.named_routes.flat_map do |_, engine_route|
|
@@ -155,6 +180,7 @@ export {};
|
|
155
180
|
end
|
156
181
|
end
|
157
182
|
|
183
|
+
sig { params(route: JourneyRoute).returns(T.untyped) }
|
158
184
|
def app_from_route(route)
|
159
185
|
app = route.app
|
160
186
|
# rails engine in Rails 4.2 use additional
|
@@ -166,6 +192,7 @@ export {};
|
|
166
192
|
end
|
167
193
|
end
|
168
194
|
|
195
|
+
sig { params(route: JourneyRoute, parent_route: T.nilable(JourneyRoute)).returns(T::Array[StringArray]) }
|
169
196
|
def route_helpers_if_match(route, parent_route = nil)
|
170
197
|
Route.new(@configuration, route, parent_route).helpers
|
171
198
|
end
|
data/lib/js_routes/middleware.rb
CHANGED
@@ -1,3 +1,6 @@
|
|
1
|
+
# typed: strict
|
2
|
+
require "js_routes/types"
|
3
|
+
|
1
4
|
module JsRoutes
|
2
5
|
# A Rack middleware that automatically updates routes file
|
3
6
|
# whenever routes.rb is modified
|
@@ -5,12 +8,17 @@ module JsRoutes
|
|
5
8
|
# Inspired by
|
6
9
|
# https://github.com/fnando/i18n-js/blob/main/lib/i18n/js/middleware.rb
|
7
10
|
class Middleware
|
11
|
+
include JsRoutes::Types
|
12
|
+
extend T::Sig
|
13
|
+
|
14
|
+
sig { params(app: T.proc.params(a0: StringHash).returns(UntypedArray)).void }
|
8
15
|
def initialize(app)
|
9
16
|
@app = app
|
10
|
-
@routes_file = Rails.root.join("config/routes.rb")
|
11
|
-
@mtime = nil
|
17
|
+
@routes_file = T.let(Rails.root.join("config/routes.rb"), Pathname)
|
18
|
+
@mtime = T.let(nil, T.nilable(Time))
|
12
19
|
end
|
13
20
|
|
21
|
+
sig { params(env: StringHash).returns(UntypedArray) }
|
14
22
|
def call(env)
|
15
23
|
update_js_routes
|
16
24
|
@app.call(env)
|
@@ -18,6 +26,7 @@ module JsRoutes
|
|
18
26
|
|
19
27
|
protected
|
20
28
|
|
29
|
+
sig { void }
|
21
30
|
def update_js_routes
|
22
31
|
new_mtime = routes_mtime
|
23
32
|
unless new_mtime == @mtime
|
@@ -26,11 +35,13 @@ module JsRoutes
|
|
26
35
|
end
|
27
36
|
end
|
28
37
|
|
38
|
+
sig { void }
|
29
39
|
def regenerate
|
30
40
|
JsRoutes.generate!
|
31
|
-
JsRoutes.definitions!
|
41
|
+
JsRoutes.definitions! if JsRoutes.configuration.modern?
|
32
42
|
end
|
33
43
|
|
44
|
+
sig { returns(T.nilable(Time)) }
|
34
45
|
def routes_mtime
|
35
46
|
File.mtime(@routes_file)
|
36
47
|
rescue Errno::ENOENT
|
data/lib/js_routes/route.rb
CHANGED
@@ -1,8 +1,16 @@
|
|
1
|
+
# typed: strict
|
2
|
+
|
3
|
+
require "js_routes/types"
|
4
|
+
require "action_dispatch/journey/route"
|
5
|
+
|
1
6
|
module JsRoutes
|
2
7
|
class Route #:nodoc:
|
3
|
-
|
4
|
-
|
5
|
-
|
8
|
+
include JsRoutes::Types
|
9
|
+
extend T::Sig
|
10
|
+
|
11
|
+
FILTERED_DEFAULT_PARTS = T.let([:controller, :action].freeze, SymbolArray)
|
12
|
+
URL_OPTIONS = T.let([:protocol, :domain, :host, :port, :subdomain].freeze, SymbolArray)
|
13
|
+
NODE_TYPES = T.let({
|
6
14
|
GROUP: 1,
|
7
15
|
CAT: 2,
|
8
16
|
SYMBOL: 3,
|
@@ -11,84 +19,108 @@ module JsRoutes
|
|
11
19
|
LITERAL: 6,
|
12
20
|
SLASH: 7,
|
13
21
|
DOT: 8
|
14
|
-
}
|
22
|
+
}.freeze, T::Hash[Symbol, Integer])
|
15
23
|
|
16
|
-
|
24
|
+
sig {returns(JsRoutes::Configuration)}
|
25
|
+
attr_reader :configuration
|
17
26
|
|
27
|
+
sig {returns(JourneyRoute)}
|
28
|
+
attr_reader :route
|
29
|
+
|
30
|
+
sig {returns(T.nilable(JourneyRoute))}
|
31
|
+
attr_reader :parent_route
|
32
|
+
|
33
|
+
sig { params(configuration: JsRoutes::Configuration, route: JourneyRoute, parent_route: T.nilable(JourneyRoute)).void }
|
18
34
|
def initialize(configuration, route, parent_route = nil)
|
19
35
|
@configuration = configuration
|
20
36
|
@route = route
|
21
37
|
@parent_route = parent_route
|
22
38
|
end
|
23
39
|
|
40
|
+
sig { returns(T::Array[StringArray]) }
|
24
41
|
def helpers
|
25
42
|
helper_types.map do |absolute|
|
26
43
|
[ documentation, helper_name(absolute), body(absolute) ]
|
27
44
|
end
|
28
45
|
end
|
29
46
|
|
47
|
+
sig {returns(T::Array[T::Boolean])}
|
30
48
|
def helper_types
|
31
49
|
return [] unless match_configuration?
|
32
|
-
@configuration
|
50
|
+
@configuration.url_links ? [true, false] : [false]
|
33
51
|
end
|
34
52
|
|
53
|
+
sig { params(absolute: T::Boolean).returns(String) }
|
35
54
|
def body(absolute)
|
36
55
|
if @configuration.dts?
|
37
56
|
definition_body
|
38
57
|
else
|
39
58
|
# For tree-shaking ESM, add a #__PURE__ comment informing Webpack/minifiers that the call to `__jsr.r`
|
40
59
|
# has no side-effects (e.g. modifying global variables) and is safe to remove when unused.
|
41
|
-
# https://webpack.js.org/guides/tree-shaking/#clarifying-tree-shaking-and-
|
60
|
+
# https://webpack.js.org/guides/tree-shaking/#clarifying-tree-shaking-and-sidyeeffects
|
42
61
|
pure_comment = @configuration.esm? ? '/*#__PURE__*/ ' : ''
|
43
62
|
"#{pure_comment}__jsr.r(#{arguments(absolute).map{|a| json(a)}.join(', ')})"
|
44
63
|
end
|
45
64
|
end
|
46
65
|
|
66
|
+
sig { returns(String) }
|
47
67
|
def definition_body
|
48
68
|
args = required_parts.map{|p| "#{apply_case(p)}: RequiredRouteParameter"}
|
49
69
|
args << "options?: #{optional_parts_type} & RouteOptions"
|
50
70
|
"((\n#{args.join(",\n").indent(2)}\n) => string) & RouteHelperExtras"
|
51
71
|
end
|
52
72
|
|
73
|
+
sig { returns(String) }
|
53
74
|
def optional_parts_type
|
54
|
-
@optional_parts_type ||=
|
55
|
-
"{" + optional_parts.map {|p| "#{p}?: OptionalRouteParameter"}.join(', ') + "}"
|
75
|
+
@optional_parts_type ||= T.let(
|
76
|
+
"{" + optional_parts.map {|p| "#{p}?: OptionalRouteParameter"}.join(', ') + "}",
|
77
|
+
T.nilable(String)
|
78
|
+
)
|
56
79
|
end
|
57
80
|
|
58
81
|
protected
|
59
82
|
|
83
|
+
|
84
|
+
sig { params(absolute: T::Boolean).returns(UntypedArray) }
|
60
85
|
def arguments(absolute)
|
61
86
|
absolute ? [*base_arguments, true] : base_arguments
|
62
87
|
end
|
63
88
|
|
89
|
+
sig { returns(T::Boolean) }
|
64
90
|
def match_configuration?
|
65
|
-
!match?(@configuration
|
91
|
+
!match?(@configuration.exclude) && match?(@configuration.include)
|
66
92
|
end
|
67
93
|
|
94
|
+
sig { returns(T.nilable(String)) }
|
68
95
|
def base_name
|
69
|
-
@base_name ||= parent_route ?
|
70
|
-
[parent_route
|
96
|
+
@base_name ||= T.let(parent_route ?
|
97
|
+
[parent_route&.name, route.name].join('_') : route.name, T.nilable(String))
|
71
98
|
end
|
72
99
|
|
100
|
+
sig { returns(T.nilable(RouteSpec)) }
|
73
101
|
def parent_spec
|
74
102
|
parent_route&.path&.spec
|
75
103
|
end
|
76
104
|
|
105
|
+
sig { returns(RouteSpec) }
|
77
106
|
def spec
|
78
107
|
route.path.spec
|
79
108
|
end
|
80
109
|
|
110
|
+
sig { params(value: T.untyped).returns(String) }
|
81
111
|
def json(value)
|
82
112
|
JsRoutes.json(value)
|
83
113
|
end
|
84
114
|
|
115
|
+
sig { params(absolute: T::Boolean).returns(String) }
|
85
116
|
def helper_name(absolute)
|
86
|
-
suffix = absolute ? :url : @configuration
|
117
|
+
suffix = absolute ? :url : @configuration.compact ? nil : :path
|
87
118
|
apply_case(base_name, suffix)
|
88
119
|
end
|
89
120
|
|
121
|
+
sig { returns(String) }
|
90
122
|
def documentation
|
91
|
-
return
|
123
|
+
return '' unless @configuration.documentation
|
92
124
|
<<-JS
|
93
125
|
/**
|
94
126
|
* Generates rails route to
|
@@ -99,18 +131,22 @@ module JsRoutes
|
|
99
131
|
JS
|
100
132
|
end
|
101
133
|
|
134
|
+
sig { returns(SymbolArray) }
|
102
135
|
def required_parts
|
103
136
|
route.required_parts
|
104
137
|
end
|
105
138
|
|
139
|
+
sig { returns(SymbolArray) }
|
106
140
|
def optional_parts
|
107
141
|
route.path.optional_names
|
108
142
|
end
|
109
143
|
|
144
|
+
sig { returns(UntypedArray) }
|
110
145
|
def base_arguments
|
111
|
-
@base_arguments ||= [parts_table, serialize(spec, parent_spec)]
|
146
|
+
@base_arguments ||= T.let([parts_table, serialize(spec, parent_spec)], T.nilable(UntypedArray))
|
112
147
|
end
|
113
148
|
|
149
|
+
sig { returns(T::Hash[Symbol, Options]) }
|
114
150
|
def parts_table
|
115
151
|
parts_table = {}
|
116
152
|
route.parts.each do |part, hash|
|
@@ -131,25 +167,29 @@ JS
|
|
131
167
|
parts_table
|
132
168
|
end
|
133
169
|
|
170
|
+
sig { returns(String) }
|
134
171
|
def documentation_params
|
135
172
|
required_parts.map do |param|
|
136
173
|
"\n * @param {any} #{apply_case(param)}"
|
137
174
|
end.join
|
138
175
|
end
|
139
176
|
|
177
|
+
sig { params(matchers: Clusivity).returns(T::Boolean) }
|
140
178
|
def match?(matchers)
|
141
179
|
Array(matchers).any? { |regex| base_name =~ regex }
|
142
180
|
end
|
143
181
|
|
182
|
+
sig { params(values: T.nilable(Literal)).returns(String) }
|
144
183
|
def apply_case(*values)
|
145
184
|
value = values.compact.map(&:to_s).join('_')
|
146
|
-
@configuration
|
185
|
+
@configuration.camel_case ? value.camelize(:lower) : value
|
147
186
|
end
|
148
187
|
|
149
188
|
# This function serializes Journey route into JSON structure
|
150
189
|
# We do not use Hash for human readable serialization
|
151
190
|
# And preffer Array serialization because it is shorter.
|
152
191
|
# Routes.js file will be smaller.
|
192
|
+
sig { params(spec: SpecNode, parent_spec: T.nilable(RouteSpec)).returns(T.nilable(T.any(UntypedArray, String))) }
|
153
193
|
def serialize(spec, parent_spec=nil)
|
154
194
|
return nil unless spec
|
155
195
|
# Rails 4 globbing requires * removal
|
@@ -168,6 +208,7 @@ JS
|
|
168
208
|
result
|
169
209
|
end
|
170
210
|
|
211
|
+
sig { params(spec: RouteSpec, parent_spec: T.nilable(RouteSpec)).returns(UntypedArray) }
|
171
212
|
def serialize_spec(spec, parent_spec = nil)
|
172
213
|
[
|
173
214
|
NODE_TYPES[spec.type],
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# typed: strict
|
2
|
+
require "action_dispatch/journey/route"
|
3
|
+
require "pathname"
|
4
|
+
require "sorbet-runtime"
|
5
|
+
|
6
|
+
module JsRoutes
|
7
|
+
module Types
|
8
|
+
extend T::Sig
|
9
|
+
|
10
|
+
UntypedArray = T.type_alias {T::Array[T.untyped]}
|
11
|
+
StringArray = T.type_alias {T::Array[String]}
|
12
|
+
SymbolArray = T.type_alias {T::Array[Symbol]}
|
13
|
+
StringHash = T.type_alias { T::Hash[String, T.untyped] }
|
14
|
+
Options = T.type_alias { T::Hash[Symbol, T.untyped] }
|
15
|
+
SpecNode = T.type_alias { T.any(String, RouteSpec, NilClass) }
|
16
|
+
Literal = T.type_alias { T.any(String, Symbol) }
|
17
|
+
JourneyRoute = T.type_alias{ActionDispatch::Journey::Route}
|
18
|
+
RouteSpec = T.type_alias {T.untyped}
|
19
|
+
Application = T.type_alias { T.any(T::Class[Rails::Engine], Rails::Application) }
|
20
|
+
ApplicationCaller = T.type_alias { T.proc.returns(Application) }
|
21
|
+
Clusivity = T.type_alias { T.any(Regexp, T::Array[Regexp]) }
|
22
|
+
FileName = T.type_alias { T.any(String, Pathname, NilClass) }
|
23
|
+
ConfigurationBlock = T.type_alias { T.proc.params(arg0: JsRoutes::Configuration).void }
|
24
|
+
Prefix = T.type_alias do
|
25
|
+
T.any(T.proc.returns(String), String, NilClass)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
data/lib/js_routes/version.rb
CHANGED
data/lib/js_routes.rb
CHANGED
@@ -1,45 +1,58 @@
|
|
1
|
+
# typed: strict
|
1
2
|
if defined?(::Rails)
|
2
3
|
require 'js_routes/engine'
|
3
4
|
end
|
4
5
|
require 'js_routes/version'
|
5
6
|
require "js_routes/configuration"
|
6
7
|
require "js_routes/instance"
|
8
|
+
require "js_routes/types"
|
7
9
|
require 'active_support/core_ext/string/indent'
|
8
10
|
|
9
11
|
module JsRoutes
|
12
|
+
extend T::Sig
|
10
13
|
|
11
14
|
#
|
12
15
|
# API
|
13
16
|
#
|
14
17
|
|
15
18
|
class << self
|
19
|
+
include JsRoutes::Types
|
20
|
+
extend T::Sig
|
21
|
+
|
22
|
+
sig { params(block: ConfigurationBlock).void }
|
16
23
|
def setup(&block)
|
17
|
-
configuration.
|
24
|
+
configuration.setup(&block)
|
18
25
|
end
|
19
26
|
|
27
|
+
sig { returns(JsRoutes::Configuration) }
|
20
28
|
def configuration
|
21
|
-
@configuration ||= Configuration.new
|
29
|
+
@configuration ||= T.let(Configuration.new, T.nilable(JsRoutes::Configuration))
|
22
30
|
end
|
23
31
|
|
32
|
+
sig { params(opts: T.untyped).returns(String) }
|
24
33
|
def generate(**opts)
|
25
|
-
Instance.new(opts).generate
|
34
|
+
Instance.new(**opts).generate
|
26
35
|
end
|
27
36
|
|
37
|
+
sig { params(file_name: FileName, opts: T.untyped).void }
|
28
38
|
def generate!(file_name = configuration.file, **opts)
|
29
39
|
Instance.new(file: file_name, **opts).generate!
|
30
40
|
end
|
31
41
|
|
42
|
+
sig { params(opts: T.untyped).returns(String) }
|
32
43
|
def definitions(**opts)
|
33
|
-
generate(module_type: 'DTS',
|
44
|
+
generate(**opts, module_type: 'DTS',)
|
34
45
|
end
|
35
46
|
|
47
|
+
sig { params(file_name: FileName, opts: T.untyped).void }
|
36
48
|
def definitions!(file_name = nil, **opts)
|
37
49
|
file_name ||= configuration.file&.sub(%r{(\.d)?\.(j|t)s\Z}, ".d.ts")
|
38
|
-
generate!(file_name, module_type: 'DTS'
|
50
|
+
generate!(file_name, **opts, module_type: 'DTS')
|
39
51
|
end
|
40
52
|
|
41
|
-
|
42
|
-
|
53
|
+
sig { params(value: T.untyped).returns(String) }
|
54
|
+
def json(value)
|
55
|
+
ActiveSupport::JSON.encode(value)
|
43
56
|
end
|
44
57
|
end
|
45
58
|
module Generators
|
data/lib/routes.js
CHANGED
@@ -19,6 +19,14 @@ RubyVariables.WRAPPER(
|
|
19
19
|
NodeTypes[NodeTypes["DOT"] = 8] = "DOT";
|
20
20
|
})(NodeTypes || (NodeTypes = {}));
|
21
21
|
const isBrowser = typeof window !== "undefined";
|
22
|
+
const UnescapedSpecials = "-._~!$&'()*+,;=:@"
|
23
|
+
.split("")
|
24
|
+
.map((s) => s.charCodeAt(0));
|
25
|
+
const UnescapedRanges = [
|
26
|
+
["a", "z"],
|
27
|
+
["A", "Z"],
|
28
|
+
["0", "9"],
|
29
|
+
].map((range) => range.map((s) => s.charCodeAt(0)));
|
22
30
|
const ModuleReferences = {
|
23
31
|
CJS: {
|
24
32
|
define(routes) {
|
@@ -97,7 +105,6 @@ RubyVariables.WRAPPER(
|
|
97
105
|
this.name = ParametersMissing.name;
|
98
106
|
}
|
99
107
|
}
|
100
|
-
const UriEncoderSegmentRegex = /[^a-zA-Z0-9\-._~!$&'()*+,;=:@]/g;
|
101
108
|
const ReservedOptions = [
|
102
109
|
"anchor",
|
103
110
|
"trailing_slash",
|
@@ -349,7 +356,22 @@ RubyVariables.WRAPPER(
|
|
349
356
|
}
|
350
357
|
}
|
351
358
|
encode_segment(segment) {
|
352
|
-
|
359
|
+
if (segment.match(/^[a-zA-Z0-9-]$/)) {
|
360
|
+
// Performance optimization for 99% of cases
|
361
|
+
return segment;
|
362
|
+
}
|
363
|
+
return (segment.match(/./gu) || [])
|
364
|
+
.map((ch) => {
|
365
|
+
const code = ch.charCodeAt(0);
|
366
|
+
if (UnescapedRanges.find((range) => code >= range[0] && code <= range[1]) ||
|
367
|
+
UnescapedSpecials.includes(code)) {
|
368
|
+
return ch;
|
369
|
+
}
|
370
|
+
else {
|
371
|
+
return encodeURIComponent(ch);
|
372
|
+
}
|
373
|
+
})
|
374
|
+
.join("");
|
353
375
|
}
|
354
376
|
is_optional_node(node) {
|
355
377
|
return [NodeTypes.STAR, NodeTypes.SYMBOL, NodeTypes.CAT].includes(node);
|
@@ -358,7 +380,7 @@ RubyVariables.WRAPPER(
|
|
358
380
|
let key;
|
359
381
|
switch (route[0]) {
|
360
382
|
case NodeTypes.GROUP:
|
361
|
-
return
|
383
|
+
return `(${this.build_path_spec(route[1])})`;
|
362
384
|
case NodeTypes.CAT:
|
363
385
|
return (this.build_path_spec(route[1]) + this.build_path_spec(route[2]));
|
364
386
|
case NodeTypes.STAR:
|
data/lib/routes.ts
CHANGED
@@ -100,7 +100,7 @@ declare const module: { exports: any } | undefined;
|
|
100
100
|
RubyVariables.WRAPPER(
|
101
101
|
// eslint-disable-next-line
|
102
102
|
(): RouterExposedMethods => {
|
103
|
-
const hasProp = (value: unknown, key: string) =>
|
103
|
+
const hasProp = (value: unknown, key: string): boolean =>
|
104
104
|
Object.prototype.hasOwnProperty.call(value, key);
|
105
105
|
enum NodeTypes {
|
106
106
|
GROUP = 1,
|
@@ -135,6 +135,16 @@ RubyVariables.WRAPPER(
|
|
135
135
|
define: (routes: RouterExposedMethods) => void;
|
136
136
|
isSupported: () => boolean;
|
137
137
|
};
|
138
|
+
|
139
|
+
const UnescapedSpecials = "-._~!$&'()*+,;=:@"
|
140
|
+
.split("")
|
141
|
+
.map((s) => s.charCodeAt(0));
|
142
|
+
const UnescapedRanges = [
|
143
|
+
["a", "z"],
|
144
|
+
["A", "Z"],
|
145
|
+
["0", "9"],
|
146
|
+
].map((range) => range.map((s) => s.charCodeAt(0)));
|
147
|
+
|
138
148
|
const ModuleReferences: Record<ModuleType, ModuleDefinition> = {
|
139
149
|
CJS: {
|
140
150
|
define(routes) {
|
@@ -215,8 +225,6 @@ RubyVariables.WRAPPER(
|
|
215
225
|
}
|
216
226
|
}
|
217
227
|
|
218
|
-
const UriEncoderSegmentRegex = /[^a-zA-Z0-9\-._~!$&'()*+,;=:@]/g;
|
219
|
-
|
220
228
|
const ReservedOptions = [
|
221
229
|
"anchor",
|
222
230
|
"trailing_slash",
|
@@ -525,9 +533,25 @@ RubyVariables.WRAPPER(
|
|
525
533
|
}
|
526
534
|
|
527
535
|
encode_segment(segment: string): string {
|
528
|
-
|
529
|
-
|
530
|
-
|
536
|
+
if (segment.match(/^[a-zA-Z0-9-]$/)) {
|
537
|
+
// Performance optimization for 99% of cases
|
538
|
+
return segment;
|
539
|
+
}
|
540
|
+
return (segment.match(/./gu) || [])
|
541
|
+
.map((ch) => {
|
542
|
+
const code = ch.charCodeAt(0);
|
543
|
+
if (
|
544
|
+
UnescapedRanges.find(
|
545
|
+
(range) => code >= range[0] && code <= range[1]
|
546
|
+
) ||
|
547
|
+
UnescapedSpecials.includes(code)
|
548
|
+
) {
|
549
|
+
return ch;
|
550
|
+
} else {
|
551
|
+
return encodeURIComponent(ch);
|
552
|
+
}
|
553
|
+
})
|
554
|
+
.join("");
|
531
555
|
}
|
532
556
|
|
533
557
|
is_optional_node(node: NodeTypes): boolean {
|
data/sorbet/config
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
**/*.rbi linguist-vendored=true
|