Skip to content

Commit 00c033c

Browse files
committed
Extract relative_path_for and move clear_file_contributions
Move `relative_path_for` from a private method on RDoc::Server to a public method on RDoc::RDoc, eliminating the duplication with the inline logic in `parse_file`. Move `clear_file_contributions` from RDoc::Server to RDoc::Store where it naturally belongs — it operates entirely on store internals (files_hash, classes_hash, modules_hash). Add tests for Store#clear_file_contributions covering single-file removal, multi-file preservation, per-file cleanup of methods/ constants/includes, and no-op for nonexistent files.
1 parent 0f8dc74 commit 00c033c

4 files changed

Lines changed: 249 additions & 100 deletions

File tree

lib/rdoc/rdoc.rb

Lines changed: 23 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -332,20 +332,7 @@ def parse_file(filename)
332332

333333
return unless content
334334

335-
filename_path = Pathname(filename).expand_path
336-
begin
337-
relative_path = filename_path.relative_path_from @options.root
338-
rescue ArgumentError
339-
relative_path = filename_path
340-
end
341-
342-
if @options.page_dir and
343-
relative_path.to_s.start_with? @options.page_dir.to_s then
344-
relative_path =
345-
relative_path.relative_path_from @options.page_dir
346-
end
347-
348-
top_level = @store.add_file filename, relative_name: relative_path.to_s
335+
top_level = @store.add_file filename, relative_name: relative_path_for(filename)
349336

350337
parser = RDoc::Parser.for top_level, content, @options, @stats
351338

@@ -388,6 +375,28 @@ def parse_file(filename)
388375
raise e
389376
end
390377

378+
##
379+
# Returns the relative path for +filename+ against +options.root+ (and
380+
# +options.page_dir+ when set). This is the key used by RDoc::Store to
381+
# identify files.
382+
383+
def relative_path_for(filename)
384+
filename_path = Pathname(filename).expand_path
385+
begin
386+
relative_path = filename_path.relative_path_from @options.root
387+
rescue ArgumentError
388+
relative_path = filename_path
389+
end
390+
391+
if @options.page_dir &&
392+
relative_path.to_s.start_with?(@options.page_dir.to_s)
393+
relative_path =
394+
relative_path.relative_path_from @options.page_dir
395+
end
396+
397+
relative_path.to_s
398+
end
399+
391400
##
392401
# Parse each file on the command line, recursively entering directories.
393402

lib/rdoc/server.rb

Lines changed: 4 additions & 86 deletions
Original file line numberDiff line numberDiff line change
@@ -352,26 +352,6 @@ def check_for_changes
352352
true
353353
end
354354

355-
##
356-
# Returns the relative path for +filename+ matching the key used by the
357-
# store, mirroring the logic in RDoc::RDoc#parse_file.
358-
359-
def relative_path_for(filename)
360-
filename_path = Pathname(filename).expand_path
361-
begin
362-
relative_path = filename_path.relative_path_from(@options.root)
363-
rescue ArgumentError
364-
relative_path = filename_path
365-
end
366-
367-
if @options.page_dir &&
368-
relative_path.to_s.start_with?(@options.page_dir.to_s)
369-
relative_path = relative_path.relative_path_from(@options.page_dir)
370-
end
371-
372-
relative_path.to_s
373-
end
374-
375355
##
376356
# Re-parses changed files, removes deleted files from the store,
377357
# refreshes the generator, and invalidates caches.
@@ -382,8 +362,8 @@ def reparse_and_refresh(changed_files, removed_files)
382362
$stderr.puts "Removed: #{removed_files.join(', ')}"
383363
removed_files.each do |f|
384364
@file_mtimes.delete(f)
385-
relative = relative_path_for(f)
386-
clear_file_contributions(relative)
365+
relative = @rdoc.relative_path_for(f)
366+
@store.clear_file_contributions(relative)
387367
@store.remove_file(relative)
388368
end
389369
end
@@ -392,8 +372,8 @@ def reparse_and_refresh(changed_files, removed_files)
392372
$stderr.puts "Re-parsing: #{changed_files.join(', ')}"
393373
changed_files.each do |f|
394374
begin
395-
relative = relative_path_for(f)
396-
clear_file_contributions(relative)
375+
relative = @rdoc.relative_path_for(f)
376+
@store.clear_file_contributions(relative)
397377
@rdoc.parse_file(f)
398378
@file_mtimes[f] = File.mtime(f) rescue nil
399379
rescue => e
@@ -410,66 +390,4 @@ def reparse_and_refresh(changed_files, removed_files)
410390
end
411391
end
412392

413-
##
414-
# Removes a file's contributions (methods, constants, comments, etc.)
415-
# from its classes and modules. If no other files contribute to a
416-
# class or module, it is removed from the store entirely. This
417-
# prevents duplication when the file is re-parsed while preserving
418-
# shared namespaces like +RDoc+ that span many files.
419-
420-
def clear_file_contributions(relative_name)
421-
top_level = @store.files_hash[relative_name]
422-
return unless top_level
423-
424-
top_level.classes_or_modules.each do |cm|
425-
# Remove methods and attributes contributed by this file
426-
cm.method_list.reject! { |m| m.file == top_level }
427-
cm.attributes.reject! { |a| a.file == top_level }
428-
429-
# Rebuild methods_hash from remaining methods and attributes
430-
cm.methods_hash.clear
431-
cm.method_list.each { |m| cm.methods_hash[m.pretty_name] = m }
432-
cm.attributes.each { |a| cm.methods_hash[a.pretty_name] = a }
433-
434-
# Remove constants contributed by this file
435-
cm.constants.reject! { |c| c.file == top_level }
436-
cm.constants_hash.clear
437-
cm.constants.each { |c| cm.constants_hash[c.name] = c }
438-
439-
# Remove includes, extends, and aliases from this file
440-
cm.includes.reject! { |i| i.file == top_level }
441-
cm.extends.reject! { |e| e.file == top_level }
442-
cm.aliases.reject! { |a| a.file == top_level }
443-
cm.external_aliases.reject! { |a| a.file == top_level }
444-
445-
# Remove comment entries from this file and rebuild the comment
446-
if cm.is_a?(RDoc::ClassModule)
447-
cm.comment_location.reject! { |(_, loc)| loc == top_level }
448-
texts = cm.comment_location.map { |(c, _)| c.to_s }
449-
merged = texts.join("\n---\n")
450-
cm.instance_variable_set(:@comment,
451-
merged.empty? ? '' : RDoc::Comment.new(merged))
452-
end
453-
454-
# Remove this file from the class/module's file list
455-
cm.in_files.delete(top_level)
456-
457-
# If no files contribute to this class/module anymore, remove it
458-
# from the store entirely. This handles file deletion correctly
459-
# for classes that are only defined in the deleted file, while
460-
# preserving classes that span multiple files.
461-
if cm.in_files.empty?
462-
if cm.is_a?(RDoc::NormalModule)
463-
@store.modules_hash.delete(cm.full_name)
464-
else
465-
@store.classes_hash.delete(cm.full_name)
466-
end
467-
cm.parent&.classes_hash&.delete(cm.name)
468-
cm.parent&.modules_hash&.delete(cm.name)
469-
end
470-
end
471-
472-
# Clear the TopLevel's class/module list to prevent duplicates
473-
top_level.classes_or_modules.clear
474-
end
475393
end

lib/rdoc/store.rb

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -213,6 +213,69 @@ def remove_file(relative_name)
213213
remove_classes_and_modules(top_level.classes_or_modules)
214214
end
215215

216+
##
217+
# Removes a file's contributions (methods, constants, comments, etc.)
218+
# from its classes and modules. If no other files contribute to a
219+
# class or module, it is removed from the store entirely. This
220+
# prevents duplication when the file is re-parsed while preserving
221+
# shared namespaces like +RDoc+ that span many files.
222+
223+
def clear_file_contributions(relative_name)
224+
top_level = @files_hash[relative_name]
225+
return unless top_level
226+
227+
top_level.classes_or_modules.each do |cm|
228+
# Remove methods and attributes contributed by this file
229+
cm.method_list.reject! { |m| m.file == top_level }
230+
cm.attributes.reject! { |a| a.file == top_level }
231+
232+
# Rebuild methods_hash from remaining methods and attributes
233+
cm.methods_hash.clear
234+
cm.method_list.each { |m| cm.methods_hash[m.pretty_name] = m }
235+
cm.attributes.each { |a| cm.methods_hash[a.pretty_name] = a }
236+
237+
# Remove constants contributed by this file
238+
cm.constants.reject! { |c| c.file == top_level }
239+
cm.constants_hash.clear
240+
cm.constants.each { |c| cm.constants_hash[c.name] = c }
241+
242+
# Remove includes, extends, and aliases from this file
243+
cm.includes.reject! { |i| i.file == top_level }
244+
cm.extends.reject! { |e| e.file == top_level }
245+
cm.aliases.reject! { |a| a.file == top_level }
246+
cm.external_aliases.reject! { |a| a.file == top_level }
247+
248+
# Remove comment entries from this file and rebuild the comment
249+
if cm.is_a?(RDoc::ClassModule)
250+
cm.comment_location.reject! { |(_, loc)| loc == top_level }
251+
texts = cm.comment_location.map { |(c, _)| c.to_s }
252+
merged = texts.join("\n---\n")
253+
cm.instance_variable_set(:@comment,
254+
merged.empty? ? '' : RDoc::Comment.new(merged))
255+
end
256+
257+
# Remove this file from the class/module's file list
258+
cm.in_files.delete(top_level)
259+
260+
# If no files contribute to this class/module anymore, remove it
261+
# from the store entirely. This handles file deletion correctly
262+
# for classes that are only defined in the deleted file, while
263+
# preserving classes that span multiple files.
264+
if cm.in_files.empty?
265+
if cm.is_a?(RDoc::NormalModule)
266+
@modules_hash.delete(cm.full_name)
267+
else
268+
@classes_hash.delete(cm.full_name)
269+
end
270+
cm.parent&.classes_hash&.delete(cm.name)
271+
cm.parent&.modules_hash&.delete(cm.name)
272+
end
273+
end
274+
275+
# Clear the TopLevel's class/module list to prevent duplicates
276+
top_level.classes_or_modules.clear
277+
end
278+
216279
##
217280
# Make sure any references to C variable names are resolved to the corresponding class.
218281
#

0 commit comments

Comments
 (0)