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.
Files changed (86) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +25 -9
  3. data/Gemfile +5 -0
  4. data/Readme.md +63 -47
  5. data/bin/tapioca +27 -0
  6. data/js-routes.gemspec +4 -1
  7. data/lib/js_routes/configuration.rb +80 -34
  8. data/lib/js_routes/engine.rb +2 -0
  9. data/lib/js_routes/generators/base.rb +15 -1
  10. data/lib/js_routes/generators/middleware.rb +3 -6
  11. data/lib/js_routes/generators/webpacker.rb +2 -4
  12. data/lib/js_routes/instance.rb +42 -15
  13. data/lib/js_routes/middleware.rb +14 -3
  14. data/lib/js_routes/route.rb +57 -16
  15. data/lib/js_routes/types.rb +28 -0
  16. data/lib/js_routes/version.rb +2 -1
  17. data/lib/js_routes.rb +20 -7
  18. data/lib/routes.js +25 -3
  19. data/lib/routes.ts +30 -6
  20. data/sorbet/config +4 -0
  21. data/sorbet/rbi/annotations/.gitattributes +1 -0
  22. data/sorbet/rbi/annotations/actionpack.rbi +428 -0
  23. data/sorbet/rbi/annotations/actionview.rbi +75 -0
  24. data/sorbet/rbi/annotations/activesupport.rbi +421 -0
  25. data/sorbet/rbi/annotations/railties.rbi +61 -0
  26. data/sorbet/rbi/gems/.gitattributes +1 -0
  27. data/sorbet/rbi/gems/actionpack@7.0.4.1.rbi +303 -0
  28. data/sorbet/rbi/gems/actionview@7.0.4.1.rbi +8 -0
  29. data/sorbet/rbi/gems/activesupport@7.0.4.1.rbi +16424 -0
  30. data/sorbet/rbi/gems/appraisal@2.4.1.rbi +584 -0
  31. data/sorbet/rbi/gems/builder@3.2.4.rbi +8 -0
  32. data/sorbet/rbi/gems/bump@0.10.0.rbi +168 -0
  33. data/sorbet/rbi/gems/byebug@11.1.3.rbi +3606 -0
  34. data/sorbet/rbi/gems/coderay@1.1.3.rbi +3426 -0
  35. data/sorbet/rbi/gems/concurrent-ruby@1.2.0.rbi +11570 -0
  36. data/sorbet/rbi/gems/crass@1.0.6.rbi +8 -0
  37. data/sorbet/rbi/gems/diff-lcs@1.5.0.rbi +1083 -0
  38. data/sorbet/rbi/gems/erubi@1.12.0.rbi +145 -0
  39. data/sorbet/rbi/gems/i18n@1.12.0.rbi +2296 -0
  40. data/sorbet/rbi/gems/libv8-node@16.10.0.0.rbi +8 -0
  41. data/sorbet/rbi/gems/loofah@2.19.1.rbi +8 -0
  42. data/sorbet/rbi/gems/method_source@1.0.0.rbi +272 -0
  43. data/sorbet/rbi/gems/mini_racer@0.6.3.rbi +224 -0
  44. data/sorbet/rbi/gems/minitest@5.17.0.rbi +1457 -0
  45. data/sorbet/rbi/gems/netrc@0.11.0.rbi +158 -0
  46. data/sorbet/rbi/gems/nokogiri@1.14.0.rbi +8 -0
  47. data/sorbet/rbi/gems/parallel@1.24.0.rbi +280 -0
  48. data/sorbet/rbi/gems/prettier_print@1.2.1.rbi +951 -0
  49. data/sorbet/rbi/gems/prism@0.24.0.rbi +29744 -0
  50. data/sorbet/rbi/gems/pry-byebug@3.10.1.rbi +1150 -0
  51. data/sorbet/rbi/gems/pry@0.14.2.rbi +10075 -0
  52. data/sorbet/rbi/gems/racc@1.6.2.rbi +150 -0
  53. data/sorbet/rbi/gems/rack-test@2.0.2.rbi +8 -0
  54. data/sorbet/rbi/gems/rack@2.2.6.2.rbi +5585 -0
  55. data/sorbet/rbi/gems/rails-dom-testing@2.0.3.rbi +8 -0
  56. data/sorbet/rbi/gems/rails-html-sanitizer@1.5.0.rbi +8 -0
  57. data/sorbet/rbi/gems/railties@7.0.4.1.rbi +1959 -0
  58. data/sorbet/rbi/gems/rake@13.0.6.rbi +3072 -0
  59. data/sorbet/rbi/gems/rbi@0.1.9.rbi +3006 -0
  60. data/sorbet/rbi/gems/rspec-core@3.12.0.rbi +10868 -0
  61. data/sorbet/rbi/gems/rspec-expectations@3.12.2.rbi +8100 -0
  62. data/sorbet/rbi/gems/rspec-mocks@3.12.3.rbi +5299 -0
  63. data/sorbet/rbi/gems/rspec-support@3.12.0.rbi +1611 -0
  64. data/sorbet/rbi/gems/rspec@3.12.0.rbi +82 -0
  65. data/sorbet/rbi/gems/spoom@1.2.4.rbi +3777 -0
  66. data/sorbet/rbi/gems/sprockets-rails@3.4.2.rbi +14 -0
  67. data/sorbet/rbi/gems/sprockets@4.2.0.rbi +8 -0
  68. data/sorbet/rbi/gems/syntax_tree@6.2.0.rbi +23136 -0
  69. data/sorbet/rbi/gems/tapioca@0.12.0.rbi +3510 -0
  70. data/sorbet/rbi/gems/thor@1.2.1.rbi +3956 -0
  71. data/sorbet/rbi/gems/tzinfo@2.0.5.rbi +5914 -0
  72. data/sorbet/rbi/gems/yard-sorbet@0.8.1.rbi +428 -0
  73. data/sorbet/rbi/gems/yard@0.9.34.rbi +18332 -0
  74. data/sorbet/rbi/gems/zeitwerk@2.6.8.rbi +8 -0
  75. data/sorbet/rbi/todo.rbi +31 -0
  76. data/sorbet/tapioca/config.yml +13 -0
  77. data/sorbet/tapioca/require.rb +4 -0
  78. data/spec/js_routes/default_serializer_spec.rb +1 -1
  79. data/spec/js_routes/module_types/dts_spec.rb +17 -2
  80. data/spec/js_routes/module_types/umd_spec.rb +2 -2
  81. data/spec/js_routes/options_spec.rb +6 -6
  82. data/spec/js_routes/rails_routes_compatibility_spec.rb +17 -12
  83. data/spec/js_routes/route_specification_spec.rb +1 -4
  84. data/spec/js_routes/{zzz_last_post_rails_init_spec.rb → zzz_sprockets_spec.rb} +1 -1
  85. data/spec/spec_helper.rb +4 -0
  86. metadata +82 -7
@@ -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
- def initialize(options = {})
13
- @configuration = JsRoutes.configuration.merge(options)
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
- if named_routes.empty? && application.respond_to?(:reload_routes!)
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
- if !@configuration.dts?
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' => ActionPack.version,
57
- 'DEPRECATED_GLOBBING_BEHAVIOR' => ActionPack::VERSION::MAJOR == 4 && ActionPack::VERSION::MINOR == 0,
58
- 'DEPRECATED_FALSE_PARAMETER_BEHAVIOR' => ActionPack::VERSION::MAJOR < 7,
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(@configuration.prefix),
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
- def json(string)
94
- JsRoutes.json(string)
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.to_a
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
@@ -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
@@ -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
- FILTERED_DEFAULT_PARTS = [:controller, :action]
4
- URL_OPTIONS = [:protocol, :domain, :host, :port, :subdomain]
5
- NODE_TYPES = {
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
- attr_reader :configuration, :route, :parent_route
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[:url_links] ? [true, false] : [false]
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-sideeffects
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[:exclude]) && match?(@configuration[:include])
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.name, route.name].join('_') : route.name
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[:compact] ? nil : :path
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 nil unless @configuration[:documentation]
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[:camel_case] ? value.camelize(:lower) : value
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
@@ -1,3 +1,4 @@
1
+ # typed: strict
1
2
  module JsRoutes
2
- VERSION = "2.2.7"
3
+ VERSION = "2.2.9"
3
4
  end
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.assign(&block)
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', **opts)
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', **opts)
50
+ generate!(file_name, **opts, module_type: 'DTS')
39
51
  end
40
52
 
41
- def json(string)
42
- ActiveSupport::JSON.encode(string)
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
- return segment.replace(UriEncoderSegmentRegex, (str) => encodeURIComponent(str));
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 "(" + this.build_path_spec(route[1]) + ")";
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
- return segment.replace(UriEncoderSegmentRegex, (str) =>
529
- encodeURIComponent(str)
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,4 @@
1
+ --dir
2
+ .
3
+ --ignore=tmp/
4
+ --ignore=vendor/
@@ -0,0 +1 @@
1
+ **/*.rbi linguist-vendored=true