• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

type-ruby / t-ruby / 20214493393

14 Dec 2025 09:34PM UTC coverage: 74.239% (-0.04%) from 74.274%
20214493393

push

github

yhk1038
refactor(compiler): always preserve directory structure in output

Remove preserve_structure option and implement smart path handling:
- Single source_include: exclude source dir name from output
  (src/models/user.trb → build/models/user.rb)
- Multiple source_include: include source dir name in output
  (src/models/user.trb → build/src/models/user.rb)
- Files outside source directories: preserve relative path from cwd
  (external/foo.trb → build/external/foo.rb)

Add resolve_path helper to handle macOS symlink paths (/var vs /private/var).

39 of 52 new or added lines in 2 files covered. (75.0%)

1 existing line in 1 file now uncovered.

4415 of 5947 relevant lines covered (74.24%)

1015.64 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

90.26
/lib/t_ruby/declaration_generator.rb
1
# frozen_string_literal: true
2

3
module TRuby
1✔
4
  # Generator for .d.trb type declaration files
5
  # Similar to TypeScript's .d.ts files
6
  class DeclarationGenerator
1✔
7
    DECLARATION_EXTENSION = ".d.trb"
1✔
8

9
    def initialize
1✔
10
      @parser = nil
12✔
11
    end
12

13
    # Generate declaration content from source code
14
    def generate(source)
1✔
15
      @parser = Parser.new(source)
10✔
16
      result = @parser.parse
10✔
17

18
      declarations = []
10✔
19

20
      # Add header comment
21
      declarations << "# Auto-generated type declaration file"
10✔
22
      declarations << "# Do not edit manually"
10✔
23
      declarations << ""
10✔
24

25
      # Generate type alias declarations
26
      (result[:type_aliases] || []).each do |type_alias|
10✔
27
        declarations << generate_type_alias(type_alias)
7✔
28
      end
29

30
      # Generate interface declarations
31
      (result[:interfaces] || []).each do |interface|
10✔
32
        declarations << generate_interface(interface)
2✔
33
      end
34

35
      # Generate function declarations
36
      (result[:functions] || []).each do |function|
10✔
37
        declarations << generate_function(function)
5✔
38
      end
39

40
      declarations.join("\n")
10✔
41
    end
42

43
    # Generate declaration file from a .trb source file
44
    def generate_file(input_path, output_dir = nil)
1✔
45
      unless File.exist?(input_path)
4✔
46
        raise ArgumentError, "File not found: #{input_path}"
1✔
47
      end
48

49
      unless input_path.end_with?(".trb")
3✔
50
        raise ArgumentError, "Expected .trb file, got: #{input_path}"
1✔
51
      end
52

53
      source = File.read(input_path)
2✔
54
      content = generate(source)
2✔
55

56
      # Determine output path
57
      output_dir ||= File.dirname(input_path)
2✔
58
      FileUtils.mkdir_p(output_dir)
2✔
59

60
      base_name = File.basename(input_path, ".trb")
2✔
61
      output_path = File.join(output_dir, "#{base_name}#{DECLARATION_EXTENSION}")
2✔
62

63
      File.write(output_path, content)
2✔
64
      output_path
2✔
65
    end
66

67
    # Generate declaration file to a specific output path
68
    def generate_file_to_path(input_path, output_path)
1✔
NEW
69
      unless File.exist?(input_path)
×
NEW
70
        raise ArgumentError, "File not found: #{input_path}"
×
71
      end
72

NEW
73
      unless input_path.end_with?(".trb")
×
NEW
74
        raise ArgumentError, "Expected .trb file, got: #{input_path}"
×
75
      end
76

NEW
77
      source = File.read(input_path)
×
NEW
78
      content = generate(source)
×
79

NEW
80
      File.write(output_path, content)
×
NEW
81
      output_path
×
82
    end
83

84
    private
1✔
85

86
    def generate_type_alias(type_alias)
1✔
87
      "type #{type_alias[:name]} = #{type_alias[:definition]}"
7✔
88
    end
89

90
    def generate_interface(interface)
1✔
91
      lines = []
2✔
92
      lines << "interface #{interface[:name]}"
2✔
93

94
      interface[:members].each do |member|
2✔
95
        lines << "  #{member[:name]}: #{member[:type]}"
4✔
96
      end
97

98
      lines << "end"
2✔
99
      lines.join("\n")
2✔
100
    end
101

102
    def generate_function(function)
1✔
103
      params = function[:params].map do |param|
5✔
104
        if param[:type]
7✔
105
          "#{param[:name]}: #{param[:type]}"
5✔
106
        else
107
          param[:name]
2✔
108
        end
109
      end.join(", ")
110

111
      return_type = function[:return_type] ? ": #{function[:return_type]}" : ""
5✔
112

113
      "def #{function[:name]}(#{params})#{return_type}"
5✔
114
    end
115
  end
116

117
  # Parser for .d.trb declaration files
118
  class DeclarationParser
1✔
119
    attr_reader :type_aliases, :interfaces, :functions
1✔
120

121
    def initialize
1✔
122
      @type_aliases = {}
105✔
123
      @interfaces = {}
105✔
124
      @functions = {}
105✔
125
    end
126

127
    # Parse a declaration file content (resets existing data)
128
    def parse(content)
1✔
129
      @type_aliases = {}
15✔
130
      @interfaces = {}
15✔
131
      @functions = {}
15✔
132

133
      parse_and_merge(content)
15✔
134
    end
135

136
    # Parse content and merge with existing data
137
    def parse_and_merge(content)
1✔
138
      parser = Parser.new(content)
19✔
139
      result = parser.parse
19✔
140

141
      # Process type aliases
142
      (result[:type_aliases] || []).each do |type_alias|
19✔
143
        @type_aliases[type_alias[:name]] = type_alias[:definition]
18✔
144
      end
145

146
      # Process interfaces
147
      (result[:interfaces] || []).each do |interface|
19✔
148
        @interfaces[interface[:name]] = interface
3✔
149
      end
150

151
      # Process functions
152
      (result[:functions] || []).each do |function|
19✔
153
        @functions[function[:name]] = function
2✔
154
      end
155

156
      self
19✔
157
    end
158

159
    # Parse a declaration file from path
160
    def parse_file(file_path)
1✔
161
      unless File.exist?(file_path)
8✔
162
        raise ArgumentError, "Declaration file not found: #{file_path}"
1✔
163
      end
164

165
      unless file_path.end_with?(DeclarationGenerator::DECLARATION_EXTENSION)
7✔
166
        raise ArgumentError, "Expected #{DeclarationGenerator::DECLARATION_EXTENSION} file, got: #{file_path}"
1✔
167
      end
168

169
      content = File.read(file_path)
6✔
170
      parse(content)
6✔
171
    end
172

173
    # Load multiple declaration files from a directory
174
    def load_directory(dir_path, recursive: false)
1✔
175
      unless Dir.exist?(dir_path)
3✔
176
        raise ArgumentError, "Directory not found: #{dir_path}"
1✔
177
      end
178

179
      pattern = recursive ? "**/*#{DeclarationGenerator::DECLARATION_EXTENSION}" : "*#{DeclarationGenerator::DECLARATION_EXTENSION}"
2✔
180
      files = Dir.glob(File.join(dir_path, pattern))
2✔
181

182
      files.each do |file|
2✔
183
        content = File.read(file)
4✔
184
        parse_and_merge(content)
4✔
185
      end
186

187
      self
2✔
188
    end
189

190
    # Check if a type is defined
191
    def type_defined?(name)
1✔
192
      @type_aliases.key?(name) || @interfaces.key?(name)
5✔
193
    end
194

195
    # Resolve a type alias
196
    def resolve_type(name)
1✔
197
      @type_aliases[name]
×
198
    end
199

200
    # Get an interface definition
201
    def get_interface(name)
1✔
202
      @interfaces[name]
×
203
    end
204

205
    # Get a function signature
206
    def get_function(name)
1✔
207
      @functions[name]
×
208
    end
209

210
    # Get all declarations as a hash
211
    def to_h
1✔
212
      {
213
        type_aliases: @type_aliases,
1✔
214
        interfaces: @interfaces,
215
        functions: @functions,
216
      }
217
    end
218

219
    # Merge another parser's declarations into this one
220
    def merge(other_parser)
1✔
221
      @type_aliases.merge!(other_parser.type_aliases)
6✔
222
      @interfaces.merge!(other_parser.interfaces)
6✔
223
      @functions.merge!(other_parser.functions)
6✔
224
      self
6✔
225
    end
226
  end
227

228
  # Loader for managing declaration files
229
  class DeclarationLoader
1✔
230
    attr_reader :search_paths
1✔
231

232
    def initialize
1✔
233
      @search_paths = []
84✔
234
      @loaded_declarations = DeclarationParser.new
84✔
235
      @loaded_files = Set.new
84✔
236
    end
237

238
    # Add a search path for declaration files
239
    def add_search_path(path)
1✔
240
      @search_paths << path unless @search_paths.include?(path)
316✔
241
      self
316✔
242
    end
243

244
    # Load a specific declaration file by name (without extension)
245
    def load(name)
1✔
246
      file_name = "#{name}#{DeclarationGenerator::DECLARATION_EXTENSION}"
4✔
247

248
      @search_paths.each do |path|
4✔
249
        full_path = File.join(path, file_name)
4✔
250
        next unless File.exist?(full_path) && !@loaded_files.include?(full_path)
4✔
251

252
        parser = DeclarationParser.new
2✔
253
        parser.parse_file(full_path)
2✔
254
        @loaded_declarations.merge(parser)
2✔
255
        @loaded_files.add(full_path)
2✔
256
        return true
2✔
257
      end
258

259
      false
2✔
260
    end
261

262
    # Load all declaration files from search paths
263
    def load_all
1✔
264
      @search_paths.each do |path|
2✔
265
        next unless Dir.exist?(path)
2✔
266

267
        Dir.glob(File.join(path, "*#{DeclarationGenerator::DECLARATION_EXTENSION}")).each do |file|
2✔
268
          next if @loaded_files.include?(file)
3✔
269

270
          parser = DeclarationParser.new
3✔
271
          parser.parse_file(file)
3✔
272
          @loaded_declarations.merge(parser)
3✔
273
          @loaded_files.add(file)
3✔
274
        end
275
      end
276

277
      self
2✔
278
    end
279

280
    # Get the combined declarations
281
    def declarations
1✔
282
      @loaded_declarations
×
283
    end
284

285
    # Check if a type is defined in any loaded declaration
286
    def type_defined?(name)
1✔
287
      @loaded_declarations.type_defined?(name)
2✔
288
    end
289

290
    # Resolve a type from loaded declarations
291
    def resolve_type(name)
1✔
292
      @loaded_declarations.resolve_type(name)
×
293
    end
294

295
    # Get all loaded type aliases
296
    def type_aliases
1✔
297
      @loaded_declarations.type_aliases
2✔
298
    end
299

300
    # Get all loaded interfaces
301
    def interfaces
1✔
302
      @loaded_declarations.interfaces
×
303
    end
304

305
    # Get all loaded functions
306
    def functions
1✔
307
      @loaded_declarations.functions
×
308
    end
309

310
    # Get list of loaded files
311
    def loaded_files
1✔
312
      @loaded_files.to_a
1✔
313
    end
314
  end
315
end
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc