jazzy 0.14.4 → 0.15.1
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/.github/workflows/Tests.yml +6 -5
 - data/.rubocop.yml +19 -0
 - data/CHANGELOG.md +56 -0
 - data/CONTRIBUTING.md +1 -1
 - data/Gemfile.lock +66 -49
 - data/README.md +115 -5
 - data/bin/sourcekitten +0 -0
 - data/jazzy.gemspec +1 -1
 - data/js/package-lock.json +6 -6
 - data/lib/jazzy/config.rb +156 -24
 - data/lib/jazzy/doc.rb +2 -2
 - data/lib/jazzy/doc_builder.rb +55 -29
 - data/lib/jazzy/doc_index.rb +185 -0
 - data/lib/jazzy/docset_builder/info_plist.mustache +1 -1
 - data/lib/jazzy/docset_builder.rb +44 -13
 - data/lib/jazzy/extensions/katex/css/katex.min.css +1 -1
 - data/lib/jazzy/extensions/katex/js/katex.min.js +1 -1
 - data/lib/jazzy/gem_version.rb +1 -1
 - data/lib/jazzy/grouper.rb +130 -0
 - data/lib/jazzy/podspec_documenter.rb +1 -1
 - data/lib/jazzy/source_declaration/type.rb +10 -2
 - data/lib/jazzy/source_declaration.rb +69 -8
 - data/lib/jazzy/source_document.rb +5 -1
 - data/lib/jazzy/source_module.rb +13 -11
 - data/lib/jazzy/sourcekitten.rb +232 -236
 - data/lib/jazzy/symbol_graph/ext_key.rb +37 -0
 - data/lib/jazzy/symbol_graph/ext_node.rb +23 -6
 - data/lib/jazzy/symbol_graph/graph.rb +31 -19
 - data/lib/jazzy/symbol_graph/relationship.rb +21 -3
 - data/lib/jazzy/symbol_graph/sym_node.rb +10 -22
 - data/lib/jazzy/symbol_graph/symbol.rb +28 -0
 - data/lib/jazzy/symbol_graph.rb +19 -16
 - data/lib/jazzy/themes/apple/assets/css/jazzy.css.scss +10 -7
 - data/lib/jazzy/themes/apple/assets/js/typeahead.jquery.js +3 -2
 - data/lib/jazzy/themes/apple/templates/doc.mustache +8 -1
 - data/lib/jazzy/themes/fullwidth/assets/css/jazzy.css.scss +5 -5
 - data/lib/jazzy/themes/fullwidth/assets/js/typeahead.jquery.js +3 -2
 - data/lib/jazzy/themes/fullwidth/templates/doc.mustache +8 -1
 - data/lib/jazzy/themes/jony/assets/css/jazzy.css.scss +6 -5
 - data/lib/jazzy/themes/jony/templates/doc.mustache +9 -2
 - data/spec/integration_spec.rb +8 -1
 - metadata +16 -7
 
    
        data/lib/jazzy/config.rb
    CHANGED
    
    | 
         @@ -13,16 +13,17 @@ module Jazzy 
     | 
|
| 
       13 
13 
     | 
    
         
             
                # rubocop:disable Naming/AccessorMethodName
         
     | 
| 
       14 
14 
     | 
    
         
             
                class Attribute
         
     | 
| 
       15 
15 
     | 
    
         
             
                  attr_reader :name, :description, :command_line, :config_file_key,
         
     | 
| 
       16 
     | 
    
         
            -
                              :default, :parse
         
     | 
| 
      
 16 
     | 
    
         
            +
                              :default, :parse, :per_module
         
     | 
| 
       17 
17 
     | 
    
         | 
| 
       18 
18 
     | 
    
         
             
                  def initialize(name, description: nil, command_line: nil,
         
     | 
| 
       19 
     | 
    
         
            -
                                 default: nil, parse: ->(x) { x })
         
     | 
| 
      
 19 
     | 
    
         
            +
                                 default: nil, parse: ->(x) { x }, per_module: false)
         
     | 
| 
       20 
20 
     | 
    
         
             
                    @name = name.to_s
         
     | 
| 
       21 
21 
     | 
    
         
             
                    @description = Array(description)
         
     | 
| 
       22 
22 
     | 
    
         
             
                    @command_line = Array(command_line)
         
     | 
| 
       23 
23 
     | 
    
         
             
                    @default = default
         
     | 
| 
       24 
24 
     | 
    
         
             
                    @parse = parse
         
     | 
| 
       25 
25 
     | 
    
         
             
                    @config_file_key = full_command_line_name || @name
         
     | 
| 
      
 26 
     | 
    
         
            +
                    @per_module = per_module
         
     | 
| 
       26 
27 
     | 
    
         
             
                  end
         
     | 
| 
       27 
28 
     | 
    
         | 
| 
       28 
29 
     | 
    
         
             
                  def get(config)
         
     | 
| 
         @@ -134,22 +135,26 @@ module Jazzy 
     | 
|
| 
       134 
135 
     | 
    
         
             
                config_attr :objc_mode,
         
     | 
| 
       135 
136 
     | 
    
         
             
                  command_line: '--[no-]objc',
         
     | 
| 
       136 
137 
     | 
    
         
             
                  description: 'Generate docs for Objective-C.',
         
     | 
| 
       137 
     | 
    
         
            -
                  default: false
         
     | 
| 
      
 138 
     | 
    
         
            +
                  default: false,
         
     | 
| 
      
 139 
     | 
    
         
            +
                  per_module: true
         
     | 
| 
       138 
140 
     | 
    
         | 
| 
       139 
141 
     | 
    
         
             
                config_attr :umbrella_header,
         
     | 
| 
       140 
142 
     | 
    
         
             
                  command_line: '--umbrella-header PATH',
         
     | 
| 
       141 
143 
     | 
    
         
             
                  description: 'Umbrella header for your Objective-C framework.',
         
     | 
| 
       142 
     | 
    
         
            -
                  parse: ->(uh) { expand_path(uh) }
         
     | 
| 
      
 144 
     | 
    
         
            +
                  parse: ->(uh) { expand_path(uh) },
         
     | 
| 
      
 145 
     | 
    
         
            +
                  per_module: true
         
     | 
| 
       143 
146 
     | 
    
         | 
| 
       144 
147 
     | 
    
         
             
                config_attr :framework_root,
         
     | 
| 
       145 
148 
     | 
    
         
             
                  command_line: '--framework-root PATH',
         
     | 
| 
       146 
149 
     | 
    
         
             
                  description: 'The root path to your Objective-C framework.',
         
     | 
| 
       147 
     | 
    
         
            -
                  parse: ->(fr) { expand_path(fr) }
         
     | 
| 
      
 150 
     | 
    
         
            +
                  parse: ->(fr) { expand_path(fr) },
         
     | 
| 
      
 151 
     | 
    
         
            +
                  per_module: true
         
     | 
| 
       148 
152 
     | 
    
         | 
| 
       149 
153 
     | 
    
         
             
                config_attr :sdk,
         
     | 
| 
       150 
154 
     | 
    
         
             
                  command_line: '--sdk [iphone|watch|appletv][os|simulator]|macosx',
         
     | 
| 
       151 
155 
     | 
    
         
             
                  description: 'The SDK for which your code should be built.',
         
     | 
| 
       152 
     | 
    
         
            -
                  default: 'macosx'
         
     | 
| 
      
 156 
     | 
    
         
            +
                  default: 'macosx',
         
     | 
| 
      
 157 
     | 
    
         
            +
                  per_module: true
         
     | 
| 
       153 
158 
     | 
    
         | 
| 
       154 
159 
     | 
    
         
             
                config_attr :hide_declarations,
         
     | 
| 
       155 
160 
     | 
    
         
             
                  command_line: '--hide-declarations [objc|swift] ',
         
     | 
| 
         @@ -175,6 +180,13 @@ module Jazzy 
     | 
|
| 
       175 
180 
     | 
    
         
             
                  command_line: ['-b', '--build-tool-arguments arg1,arg2,…argN', Array],
         
     | 
| 
       176 
181 
     | 
    
         
             
                  description: 'Arguments to forward to xcodebuild, swift build, or ' \
         
     | 
| 
       177 
182 
     | 
    
         
             
                    'sourcekitten.',
         
     | 
| 
      
 183 
     | 
    
         
            +
                  default: [],
         
     | 
| 
      
 184 
     | 
    
         
            +
                  per_module: true
         
     | 
| 
      
 185 
     | 
    
         
            +
             
     | 
| 
      
 186 
     | 
    
         
            +
                config_attr :modules,
         
     | 
| 
      
 187 
     | 
    
         
            +
                  command_line: ['--modules Mod1,Mod2,…ModN', Array],
         
     | 
| 
      
 188 
     | 
    
         
            +
                  description: 'List of modules to document.  Use the config file to set per-module ' \
         
     | 
| 
      
 189 
     | 
    
         
            +
                    "build flags, see 'Documenting multiple modules' in the README.",
         
     | 
| 
       178 
190 
     | 
    
         
             
                  default: []
         
     | 
| 
       179 
191 
     | 
    
         | 
| 
       180 
192 
     | 
    
         
             
                alias_config_attr :xcodebuild_arguments, :build_tool_arguments,
         
     | 
| 
         @@ -185,19 +197,23 @@ module Jazzy 
     | 
|
| 
       185 
197 
     | 
    
         
             
                  command_line: ['-s', '--sourcekitten-sourcefile filepath1,…filepathN',
         
     | 
| 
       186 
198 
     | 
    
         
             
                                 Array],
         
     | 
| 
       187 
199 
     | 
    
         
             
                  description: 'File(s) generated from sourcekitten output to parse',
         
     | 
| 
       188 
     | 
    
         
            -
                  parse: ->(paths) { [paths].flatten.map { |path| expand_path(path) } }
         
     | 
| 
      
 200 
     | 
    
         
            +
                  parse: ->(paths) { [paths].flatten.map { |path| expand_path(path) } },
         
     | 
| 
      
 201 
     | 
    
         
            +
                  default: [],
         
     | 
| 
      
 202 
     | 
    
         
            +
                  per_module: true
         
     | 
| 
       189 
203 
     | 
    
         | 
| 
       190 
204 
     | 
    
         
             
                config_attr :source_directory,
         
     | 
| 
       191 
205 
     | 
    
         
             
                  command_line: '--source-directory DIRPATH',
         
     | 
| 
       192 
206 
     | 
    
         
             
                  description: 'The directory that contains the source to be documented',
         
     | 
| 
       193 
207 
     | 
    
         
             
                  default: Pathname.pwd,
         
     | 
| 
       194 
     | 
    
         
            -
                  parse: ->(sd) { expand_path(sd) }
         
     | 
| 
      
 208 
     | 
    
         
            +
                  parse: ->(sd) { expand_path(sd) },
         
     | 
| 
      
 209 
     | 
    
         
            +
                  per_module: true
         
     | 
| 
       195 
210 
     | 
    
         | 
| 
       196 
211 
     | 
    
         
             
                config_attr :symbolgraph_directory,
         
     | 
| 
       197 
212 
     | 
    
         
             
                  command_line: '--symbolgraph-directory DIRPATH',
         
     | 
| 
       198 
213 
     | 
    
         
             
                  description: 'A directory containing a set of Swift Symbolgraph files ' \
         
     | 
| 
       199 
214 
     | 
    
         
             
                    'representing the module to be documented',
         
     | 
| 
       200 
     | 
    
         
            -
                  parse: ->(sd) { expand_path(sd) }
         
     | 
| 
      
 215 
     | 
    
         
            +
                  parse: ->(sd) { expand_path(sd) },
         
     | 
| 
      
 216 
     | 
    
         
            +
                  per_module: true
         
     | 
| 
       201 
217 
     | 
    
         | 
| 
       202 
218 
     | 
    
         
             
                config_attr :excluded_files,
         
     | 
| 
       203 
219 
     | 
    
         
             
                  command_line: ['-e', '--exclude filepath1,filepath2,…filepathN', Array],
         
     | 
| 
         @@ -261,7 +277,8 @@ module Jazzy 
     | 
|
| 
       261 
277 
     | 
    
         
             
                config_attr :module_name,
         
     | 
| 
       262 
278 
     | 
    
         
             
                  command_line: ['-m', '--module MODULE_NAME'],
         
     | 
| 
       263 
279 
     | 
    
         
             
                  description: 'Name of module being documented. (e.g. RealmSwift)',
         
     | 
| 
       264 
     | 
    
         
            -
                  default: ''
         
     | 
| 
      
 280 
     | 
    
         
            +
                  default: '',
         
     | 
| 
      
 281 
     | 
    
         
            +
                  per_module: true
         
     | 
| 
       265 
282 
     | 
    
         | 
| 
       266 
283 
     | 
    
         
             
                config_attr :version,
         
     | 
| 
       267 
284 
     | 
    
         
             
                  command_line: '--module-version VERSION',
         
     | 
| 
         @@ -284,6 +301,10 @@ module Jazzy 
     | 
|
| 
       284 
301 
     | 
    
         
             
                  description: 'The path to a markdown README file',
         
     | 
| 
       285 
302 
     | 
    
         
             
                  parse: ->(rp) { expand_path(rp) }
         
     | 
| 
       286 
303 
     | 
    
         | 
| 
      
 304 
     | 
    
         
            +
                config_attr :readme_title,
         
     | 
| 
      
 305 
     | 
    
         
            +
                  command_line: '--readme-title TITLE',
         
     | 
| 
      
 306 
     | 
    
         
            +
                  description: 'The title for the README in the generated documentation'
         
     | 
| 
      
 307 
     | 
    
         
            +
             
     | 
| 
       287 
308 
     | 
    
         
             
                config_attr :documentation_glob,
         
     | 
| 
       288 
309 
     | 
    
         
             
                  command_line: '--documentation GLOB',
         
     | 
| 
       289 
310 
     | 
    
         
             
                  description: 'Glob that matches available documentation',
         
     | 
| 
         @@ -317,6 +338,13 @@ module Jazzy 
     | 
|
| 
       317 
338 
     | 
    
         
             
                  command_line: '--docset-path DIRPATH',
         
     | 
| 
       318 
339 
     | 
    
         
             
                  description: 'The relative path for the generated docset'
         
     | 
| 
       319 
340 
     | 
    
         | 
| 
      
 341 
     | 
    
         
            +
                config_attr :docset_title,
         
     | 
| 
      
 342 
     | 
    
         
            +
                  command_line: '--docset-title TITLE',
         
     | 
| 
      
 343 
     | 
    
         
            +
                  description: 'The title of the generated docset.  A simplified version ' \
         
     | 
| 
      
 344 
     | 
    
         
            +
                    'is used for the filenames associated with the docset.  If the ' \
         
     | 
| 
      
 345 
     | 
    
         
            +
                    'option is not set then the name of the module being documented is ' \
         
     | 
| 
      
 346 
     | 
    
         
            +
                    'used as the docset title.'
         
     | 
| 
      
 347 
     | 
    
         
            +
             
     | 
| 
       320 
348 
     | 
    
         
             
                # ──────── URLs ────────
         
     | 
| 
       321 
349 
     | 
    
         | 
| 
       322 
350 
     | 
    
         
             
                config_attr :root_url,
         
     | 
| 
         @@ -488,6 +516,23 @@ module Jazzy 
     | 
|
| 
       488 
516 
     | 
    
         
             
                    '--min-acl is set to `public` or `open`.',
         
     | 
| 
       489 
517 
     | 
    
         
             
                  default: false
         
     | 
| 
       490 
518 
     | 
    
         | 
| 
      
 519 
     | 
    
         
            +
                MERGE_MODULES = %w[all extensions none].freeze
         
     | 
| 
      
 520 
     | 
    
         
            +
             
     | 
| 
      
 521 
     | 
    
         
            +
                config_attr :merge_modules,
         
     | 
| 
      
 522 
     | 
    
         
            +
                  command_line: "--merge-modules #{MERGE_MODULES.join(' | ')}",
         
     | 
| 
      
 523 
     | 
    
         
            +
                  description: 'Control how to display declarations from multiple ' \
         
     | 
| 
      
 524 
     | 
    
         
            +
                    'modules.  `all`, the default, places all declarations of the ' \
         
     | 
| 
      
 525 
     | 
    
         
            +
                    "same kind together.  `none` keeps each module's declarations " \
         
     | 
| 
      
 526 
     | 
    
         
            +
                    'separate.  `extensions` is like `none` but merges ' \
         
     | 
| 
      
 527 
     | 
    
         
            +
                    'cross-module extensions into their extended type.',
         
     | 
| 
      
 528 
     | 
    
         
            +
                  default: 'all',
         
     | 
| 
      
 529 
     | 
    
         
            +
                  parse: ->(merge) do
         
     | 
| 
      
 530 
     | 
    
         
            +
                    return merge.to_sym if MERGE_MODULES.include?(merge)
         
     | 
| 
      
 531 
     | 
    
         
            +
             
     | 
| 
      
 532 
     | 
    
         
            +
                    raise "Unsupported merge_modules #{merge}, " \
         
     | 
| 
      
 533 
     | 
    
         
            +
                      "supported values: #{MERGE_MODULES.join(', ')}"
         
     | 
| 
      
 534 
     | 
    
         
            +
                  end
         
     | 
| 
      
 535 
     | 
    
         
            +
             
     | 
| 
       491 
536 
     | 
    
         
             
                # rubocop:enable Layout/ArgumentAlignment
         
     | 
| 
       492 
537 
     | 
    
         | 
| 
       493 
538 
     | 
    
         
             
                def initialize
         
     | 
| 
         @@ -507,12 +552,7 @@ module Jazzy 
     | 
|
| 
       507 
552 
     | 
    
         
             
                  config.parse_config_file
         
     | 
| 
       508 
553 
     | 
    
         
             
                  PodspecDocumenter.apply_config_defaults(config.podspec, config)
         
     | 
| 
       509 
554 
     | 
    
         | 
| 
       510 
     | 
    
         
            -
                   
     | 
| 
       511 
     | 
    
         
            -
                    config.dash_url ||= URI.join(
         
     | 
| 
       512 
     | 
    
         
            -
                      config.root_url,
         
     | 
| 
       513 
     | 
    
         
            -
                      "docsets/#{config.module_name}.xml",
         
     | 
| 
       514 
     | 
    
         
            -
                    )
         
     | 
| 
       515 
     | 
    
         
            -
                  end
         
     | 
| 
      
 555 
     | 
    
         
            +
                  config.set_module_configs
         
     | 
| 
       516 
556 
     | 
    
         | 
| 
       517 
557 
     | 
    
         
             
                  config.validate
         
     | 
| 
       518 
558 
     | 
    
         | 
| 
         @@ -569,11 +609,13 @@ module Jazzy 
     | 
|
| 
       569 
609 
     | 
    
         
             
                  puts "Using config file #{config_path}"
         
     | 
| 
       570 
610 
     | 
    
         
             
                  config_file = read_config_file(config_path)
         
     | 
| 
       571 
611 
     | 
    
         | 
| 
       572 
     | 
    
         
            -
                  attrs_by_conf_key, attrs_by_name =  
     | 
| 
       573 
     | 
    
         
            -
                    self.class.all_config_attrs.group_by(&prop)
         
     | 
| 
       574 
     | 
    
         
            -
                  end
         
     | 
| 
      
 612 
     | 
    
         
            +
                  attrs_by_conf_key, attrs_by_name = grouped_attributes
         
     | 
| 
       575 
613 
     | 
    
         | 
| 
       576 
     | 
    
         
            -
                  config_file 
     | 
| 
      
 614 
     | 
    
         
            +
                  parse_config_hash(config_file, attrs_by_conf_key, attrs_by_name)
         
     | 
| 
      
 615 
     | 
    
         
            +
                end
         
     | 
| 
      
 616 
     | 
    
         
            +
             
     | 
| 
      
 617 
     | 
    
         
            +
                def parse_config_hash(hash, attrs_by_conf_key, attrs_by_name, override: false)
         
     | 
| 
      
 618 
     | 
    
         
            +
                  hash.each do |key, value|
         
     | 
| 
       577 
619 
     | 
    
         
             
                    unless attr = attrs_by_conf_key[key]
         
     | 
| 
       578 
620 
     | 
    
         
             
                      message = "Unknown config file attribute #{key.inspect}"
         
     | 
| 
       579 
621 
     | 
    
         
             
                      if matching_name = attrs_by_name[key]
         
     | 
| 
         @@ -583,11 +625,19 @@ module Jazzy 
     | 
|
| 
       583 
625 
     | 
    
         
             
                      warning message
         
     | 
| 
       584 
626 
     | 
    
         
             
                      next
         
     | 
| 
       585 
627 
     | 
    
         
             
                    end
         
     | 
| 
       586 
     | 
    
         
            -
             
     | 
| 
       587 
     | 
    
         
            -
                    attr.first. 
     | 
| 
      
 628 
     | 
    
         
            +
                    setter = override ? :set : :set_if_unconfigured
         
     | 
| 
      
 629 
     | 
    
         
            +
                    attr.first.method(setter).call(self, value)
         
     | 
| 
       588 
630 
     | 
    
         
             
                  end
         
     | 
| 
      
 631 
     | 
    
         
            +
                end
         
     | 
| 
       589 
632 
     | 
    
         | 
| 
       590 
     | 
    
         
            -
             
     | 
| 
      
 633 
     | 
    
         
            +
                # Find keyed versions of the attributes, by config file key and then name-in-code
         
     | 
| 
      
 634 
     | 
    
         
            +
                # Optional block allows filtering/overriding of attribute list.
         
     | 
| 
      
 635 
     | 
    
         
            +
                def grouped_attributes
         
     | 
| 
      
 636 
     | 
    
         
            +
                  attrs = self.class.all_config_attrs
         
     | 
| 
      
 637 
     | 
    
         
            +
                  attrs = yield attrs if block_given?
         
     | 
| 
      
 638 
     | 
    
         
            +
                  %i[config_file_key name].map do |property|
         
     | 
| 
      
 639 
     | 
    
         
            +
                    attrs.group_by(&property)
         
     | 
| 
      
 640 
     | 
    
         
            +
                  end
         
     | 
| 
       591 
641 
     | 
    
         
             
                end
         
     | 
| 
       592 
642 
     | 
    
         | 
| 
       593 
643 
     | 
    
         
             
                def validate
         
     | 
| 
         @@ -598,6 +648,19 @@ module Jazzy 
     | 
|
| 
       598 
648 
     | 
    
         
             
                      '`source_host_url` or `source_host_files_url`.'
         
     | 
| 
       599 
649 
     | 
    
         
             
                  end
         
     | 
| 
       600 
650 
     | 
    
         | 
| 
      
 651 
     | 
    
         
            +
                  if modules_configured && module_name_configured
         
     | 
| 
      
 652 
     | 
    
         
            +
                    raise 'Options `modules` and `module` are both set which is not supported. ' \
         
     | 
| 
      
 653 
     | 
    
         
            +
                      'To document multiple modules, use just `modules`.'
         
     | 
| 
      
 654 
     | 
    
         
            +
                  end
         
     | 
| 
      
 655 
     | 
    
         
            +
             
     | 
| 
      
 656 
     | 
    
         
            +
                  if modules_configured && podspec_configured
         
     | 
| 
      
 657 
     | 
    
         
            +
                    raise 'Options `modules` and `podspec` are both set which is not supported.'
         
     | 
| 
      
 658 
     | 
    
         
            +
                  end
         
     | 
| 
      
 659 
     | 
    
         
            +
             
     | 
| 
      
 660 
     | 
    
         
            +
                  module_configs.each(&:validate_module)
         
     | 
| 
      
 661 
     | 
    
         
            +
                end
         
     | 
| 
      
 662 
     | 
    
         
            +
             
     | 
| 
      
 663 
     | 
    
         
            +
                def validate_module
         
     | 
| 
       601 
664 
     | 
    
         
             
                  if objc_mode &&
         
     | 
| 
       602 
665 
     | 
    
         
             
                     build_tool_arguments_configured &&
         
     | 
| 
       603 
666 
     | 
    
         
             
                     (framework_root_configured || umbrella_header_configured)
         
     | 
| 
         @@ -608,6 +671,76 @@ module Jazzy 
     | 
|
| 
       608 
671 
     | 
    
         | 
| 
       609 
672 
     | 
    
         
             
                # rubocop:enable Metrics/MethodLength
         
     | 
| 
       610 
673 
     | 
    
         | 
| 
      
 674 
     | 
    
         
            +
                # Module Configs
         
     | 
| 
      
 675 
     | 
    
         
            +
                #
         
     | 
| 
      
 676 
     | 
    
         
            +
                # The user can enter module information in three different ways.  This
         
     | 
| 
      
 677 
     | 
    
         
            +
                # consolidates them into one view for the rest of the code.
         
     | 
| 
      
 678 
     | 
    
         
            +
                #
         
     | 
| 
      
 679 
     | 
    
         
            +
                # 1) Single module, back-compatible
         
     | 
| 
      
 680 
     | 
    
         
            +
                #    --module Foo etc etc (or not given at all)
         
     | 
| 
      
 681 
     | 
    
         
            +
                #
         
     | 
| 
      
 682 
     | 
    
         
            +
                # 2) Multiple modules, simple, sharing build params
         
     | 
| 
      
 683 
     | 
    
         
            +
                #    --modules Foo,Bar,Baz --source-directory Xyz
         
     | 
| 
      
 684 
     | 
    
         
            +
                #
         
     | 
| 
      
 685 
     | 
    
         
            +
                # 3) Multiple modules, custom, different build params but
         
     | 
| 
      
 686 
     | 
    
         
            +
                #    inheriting others from the top level.
         
     | 
| 
      
 687 
     | 
    
         
            +
                #    This is config-file only.
         
     | 
| 
      
 688 
     | 
    
         
            +
                #    - modules
         
     | 
| 
      
 689 
     | 
    
         
            +
                #      - module: Foo
         
     | 
| 
      
 690 
     | 
    
         
            +
                #        source_directory: Xyz
         
     | 
| 
      
 691 
     | 
    
         
            +
                #        build_tool_arguments: [a, b, c]
         
     | 
| 
      
 692 
     | 
    
         
            +
                #
         
     | 
| 
      
 693 
     | 
    
         
            +
                # After this we're left with `config.module_configs` that is an
         
     | 
| 
      
 694 
     | 
    
         
            +
                # array of `Config` objects.
         
     | 
| 
      
 695 
     | 
    
         
            +
             
     | 
| 
      
 696 
     | 
    
         
            +
                attr_reader :module_configs
         
     | 
| 
      
 697 
     | 
    
         
            +
                attr_reader :module_names
         
     | 
| 
      
 698 
     | 
    
         
            +
             
     | 
| 
      
 699 
     | 
    
         
            +
                def set_module_configs
         
     | 
| 
      
 700 
     | 
    
         
            +
                  @module_configs = parse_module_configs
         
     | 
| 
      
 701 
     | 
    
         
            +
                  @module_names = module_configs.map(&:module_name)
         
     | 
| 
      
 702 
     | 
    
         
            +
                  @module_names_set = Set.new(module_names)
         
     | 
| 
      
 703 
     | 
    
         
            +
                end
         
     | 
| 
      
 704 
     | 
    
         
            +
             
     | 
| 
      
 705 
     | 
    
         
            +
                def module_name?(name)
         
     | 
| 
      
 706 
     | 
    
         
            +
                  @module_names_set.include?(name)
         
     | 
| 
      
 707 
     | 
    
         
            +
                end
         
     | 
| 
      
 708 
     | 
    
         
            +
             
     | 
| 
      
 709 
     | 
    
         
            +
                def multiple_modules?
         
     | 
| 
      
 710 
     | 
    
         
            +
                  @module_names.count > 1
         
     | 
| 
      
 711 
     | 
    
         
            +
                end
         
     | 
| 
      
 712 
     | 
    
         
            +
             
     | 
| 
      
 713 
     | 
    
         
            +
                def parse_module_configs
         
     | 
| 
      
 714 
     | 
    
         
            +
                  return [self] unless modules_configured
         
     | 
| 
      
 715 
     | 
    
         
            +
             
     | 
| 
      
 716 
     | 
    
         
            +
                  raise 'Config file key `modules` must be an array' unless modules.is_a?(Array)
         
     | 
| 
      
 717 
     | 
    
         
            +
             
     | 
| 
      
 718 
     | 
    
         
            +
                  if modules.first.is_a?(String)
         
     | 
| 
      
 719 
     | 
    
         
            +
                    # Massage format (2) into (3)
         
     | 
| 
      
 720 
     | 
    
         
            +
                    self.modules = modules.map { |mod| { 'module' => mod } }
         
     | 
| 
      
 721 
     | 
    
         
            +
                  end
         
     | 
| 
      
 722 
     | 
    
         
            +
             
     | 
| 
      
 723 
     | 
    
         
            +
                  # Allow per-module overrides of only some config options
         
     | 
| 
      
 724 
     | 
    
         
            +
                  attrs_by_conf_key, attrs_by_name =
         
     | 
| 
      
 725 
     | 
    
         
            +
                    grouped_attributes { |attr| attr.select(&:per_module) }
         
     | 
| 
      
 726 
     | 
    
         
            +
             
     | 
| 
      
 727 
     | 
    
         
            +
                  modules.map do |module_hash|
         
     | 
| 
      
 728 
     | 
    
         
            +
                    mod_name = module_hash['module'] || ''
         
     | 
| 
      
 729 
     | 
    
         
            +
                    raise 'Missing `modules.module` config key' if mod_name.empty?
         
     | 
| 
      
 730 
     | 
    
         
            +
             
     | 
| 
      
 731 
     | 
    
         
            +
                    dup.tap do |module_config|
         
     | 
| 
      
 732 
     | 
    
         
            +
                      module_config.parse_config_hash(
         
     | 
| 
      
 733 
     | 
    
         
            +
                        module_hash, attrs_by_conf_key, attrs_by_name, override: true
         
     | 
| 
      
 734 
     | 
    
         
            +
                      )
         
     | 
| 
      
 735 
     | 
    
         
            +
                    end
         
     | 
| 
      
 736 
     | 
    
         
            +
                  end
         
     | 
| 
      
 737 
     | 
    
         
            +
                end
         
     | 
| 
      
 738 
     | 
    
         
            +
             
     | 
| 
      
 739 
     | 
    
         
            +
                # For podspec query
         
     | 
| 
      
 740 
     | 
    
         
            +
                def module_name_known?
         
     | 
| 
      
 741 
     | 
    
         
            +
                  module_name_configured || modules_configured
         
     | 
| 
      
 742 
     | 
    
         
            +
                end
         
     | 
| 
      
 743 
     | 
    
         
            +
             
     | 
| 
       611 
744 
     | 
    
         
             
                def locate_config_file
         
     | 
| 
       612 
745 
     | 
    
         
             
                  return config_file if config_file
         
     | 
| 
       613 
746 
     | 
    
         | 
| 
         @@ -615,7 +748,6 @@ module Jazzy 
     | 
|
| 
       615 
748 
     | 
    
         
             
                    candidate = dir.join('.jazzy.yaml')
         
     | 
| 
       616 
749 
     | 
    
         
             
                    return candidate if candidate.exist?
         
     | 
| 
       617 
750 
     | 
    
         
             
                  end
         
     | 
| 
       618 
     | 
    
         
            -
             
     | 
| 
       619 
751 
     | 
    
         
             
                  nil
         
     | 
| 
       620 
752 
     | 
    
         
             
                end
         
     | 
| 
       621 
753 
     | 
    
         | 
    
        data/lib/jazzy/doc.rb
    CHANGED
    
    | 
         @@ -48,9 +48,9 @@ module Jazzy 
     | 
|
| 
       48 
48 
     | 
    
         
             
                  elsif config.version_configured
         
     | 
| 
       49 
49 
     | 
    
         
             
                    # Fake version for integration tests
         
     | 
| 
       50 
50 
     | 
    
         
             
                    version = ENV['JAZZY_FAKE_MODULE_VERSION'] || config.version
         
     | 
| 
       51 
     | 
    
         
            -
                    "#{config.module_name} #{version} Docs"
         
     | 
| 
      
 51 
     | 
    
         
            +
                    "#{config.module_configs.first.module_name} #{version} Docs"
         
     | 
| 
       52 
52 
     | 
    
         
             
                  else
         
     | 
| 
       53 
     | 
    
         
            -
                    "#{config.module_name} Docs"
         
     | 
| 
      
 53 
     | 
    
         
            +
                    "#{config.module_configs.first.module_name} Docs"
         
     | 
| 
       54 
54 
     | 
    
         
             
                  end
         
     | 
| 
       55 
55 
     | 
    
         
             
                end
         
     | 
| 
       56 
56 
     | 
    
         | 
    
        data/lib/jazzy/doc_builder.rb
    CHANGED
    
    | 
         @@ -67,32 +67,32 @@ module Jazzy 
     | 
|
| 
       67 
67 
     | 
    
         | 
| 
       68 
68 
     | 
    
         
             
                # Build documentation from the given options
         
     | 
| 
       69 
69 
     | 
    
         
             
                # @param [Config] options
         
     | 
| 
       70 
     | 
    
         
            -
                # @return [SourceModule] the documented source module
         
     | 
| 
       71 
70 
     | 
    
         
             
                def self.build(options)
         
     | 
| 
       72 
     | 
    
         
            -
                   
     | 
| 
       73 
     | 
    
         
            -
                     
     | 
| 
       74 
     | 
    
         
            -
             
     | 
| 
       75 
     | 
    
         
            -
             
     | 
| 
       76 
     | 
    
         
            -
             
     | 
| 
       77 
     | 
    
         
            -
             
     | 
| 
       78 
     | 
    
         
            -
             
     | 
| 
       79 
     | 
    
         
            -
             
     | 
| 
       80 
     | 
    
         
            -
             
     | 
| 
       81 
     | 
    
         
            -
             
     | 
| 
       82 
     | 
    
         
            -
                       
     | 
| 
      
 71 
     | 
    
         
            +
                  module_jsons = options.module_configs.map do |module_config|
         
     | 
| 
      
 72 
     | 
    
         
            +
                    if module_config.podspec_configured
         
     | 
| 
      
 73 
     | 
    
         
            +
                      # Config#validate guarantees not multi-module here
         
     | 
| 
      
 74 
     | 
    
         
            +
                      pod_documenter = PodspecDocumenter.new(options.podspec)
         
     | 
| 
      
 75 
     | 
    
         
            +
                      pod_documenter.sourcekitten_output(options)
         
     | 
| 
      
 76 
     | 
    
         
            +
                    elsif !module_config.sourcekitten_sourcefile.empty?
         
     | 
| 
      
 77 
     | 
    
         
            +
                      "[#{module_config.sourcekitten_sourcefile.map(&:read).join(',')}]"
         
     | 
| 
      
 78 
     | 
    
         
            +
                    elsif module_config.swift_build_tool == :symbolgraph
         
     | 
| 
      
 79 
     | 
    
         
            +
                      SymbolGraph.build(module_config)
         
     | 
| 
      
 80 
     | 
    
         
            +
                    else
         
     | 
| 
      
 81 
     | 
    
         
            +
                      Dir.chdir(module_config.source_directory) do
         
     | 
| 
      
 82 
     | 
    
         
            +
                        arguments = SourceKitten.arguments_from_options(module_config)
         
     | 
| 
      
 83 
     | 
    
         
            +
                        SourceKitten.run_sourcekitten(arguments)
         
     | 
| 
      
 84 
     | 
    
         
            +
                      end
         
     | 
| 
       83 
85 
     | 
    
         
             
                    end
         
     | 
| 
       84 
86 
     | 
    
         
             
                  end
         
     | 
| 
       85 
87 
     | 
    
         | 
| 
       86 
     | 
    
         
            -
                  build_docs_for_sourcekitten_output( 
     | 
| 
      
 88 
     | 
    
         
            +
                  build_docs_for_sourcekitten_output(module_jsons, options)
         
     | 
| 
       87 
89 
     | 
    
         
             
                end
         
     | 
| 
       88 
90 
     | 
    
         | 
| 
       89 
91 
     | 
    
         
             
                # Build & write HTML docs to disk from structured docs array
         
     | 
| 
       90 
92 
     | 
    
         
             
                # @param [String] output_dir Root directory to write docs
         
     | 
| 
       91 
     | 
    
         
            -
                # @param [ 
     | 
| 
       92 
     | 
    
         
            -
                 
     | 
| 
       93 
     | 
    
         
            -
             
     | 
| 
       94 
     | 
    
         
            -
                def self.build_docs(output_dir, docs, source_module)
         
     | 
| 
       95 
     | 
    
         
            -
                  each_doc(output_dir, docs) do |doc, path|
         
     | 
| 
      
 93 
     | 
    
         
            +
                # @param [SourceModule] source_module All info to generate docs
         
     | 
| 
      
 94 
     | 
    
         
            +
                def self.build_docs(output_dir, source_module)
         
     | 
| 
      
 95 
     | 
    
         
            +
                  each_doc(output_dir, source_module.docs) do |doc, path|
         
     | 
| 
       96 
96 
     | 
    
         
             
                    prepare_output_dir(path.parent, false)
         
     | 
| 
       97 
97 
     | 
    
         
             
                    depth = path.relative_path_from(output_dir).each_filename.count - 1
         
     | 
| 
       98 
98 
     | 
    
         
             
                    path_to_root = '../' * depth
         
     | 
| 
         @@ -124,10 +124,13 @@ module Jazzy 
     | 
|
| 
       124 
124 
     | 
    
         | 
| 
       125 
125 
     | 
    
         
             
                  docs << SourceDocument.make_index(options.readme_path)
         
     | 
| 
       126 
126 
     | 
    
         | 
| 
       127 
     | 
    
         
            -
                  source_module = SourceModule.new(options, docs, structure, coverage)
         
     | 
| 
       128 
     | 
    
         
            -
             
     | 
| 
       129 
127 
     | 
    
         
             
                  output_dir = options.output
         
     | 
| 
       130 
     | 
    
         
            -
             
     | 
| 
      
 128 
     | 
    
         
            +
             
     | 
| 
      
 129 
     | 
    
         
            +
                  docset_builder = DocsetBuilder.new(output_dir)
         
     | 
| 
      
 130 
     | 
    
         
            +
             
     | 
| 
      
 131 
     | 
    
         
            +
                  source_module = SourceModule.new(docs, structure, coverage, docset_builder)
         
     | 
| 
      
 132 
     | 
    
         
            +
             
     | 
| 
      
 133 
     | 
    
         
            +
                  build_docs(output_dir, source_module)
         
     | 
| 
       131 
134 
     | 
    
         | 
| 
       132 
135 
     | 
    
         
             
                  unless options.disable_search
         
     | 
| 
       133 
136 
     | 
    
         
             
                    warn 'building search index'
         
     | 
| 
         @@ -137,7 +140,7 @@ module Jazzy 
     | 
|
| 
       137 
140 
     | 
    
         
             
                  copy_extensions(source_module, output_dir)
         
     | 
| 
       138 
141 
     | 
    
         
             
                  copy_theme_assets(output_dir)
         
     | 
| 
       139 
142 
     | 
    
         | 
| 
       140 
     | 
    
         
            -
                   
     | 
| 
      
 143 
     | 
    
         
            +
                  docset_builder.build!(source_module.all_declarations)
         
     | 
| 
       141 
144 
     | 
    
         | 
| 
       142 
145 
     | 
    
         
             
                  generate_badge(source_module.doc_coverage, options)
         
     | 
| 
       143 
146 
     | 
    
         | 
| 
         @@ -148,14 +151,12 @@ module Jazzy 
     | 
|
| 
       148 
151 
     | 
    
         
             
                end
         
     | 
| 
       149 
152 
     | 
    
         | 
| 
       150 
153 
     | 
    
         
             
                # Build docs given sourcekitten output
         
     | 
| 
       151 
     | 
    
         
            -
                # @param [String] sourcekitten_output Output of sourcekitten command
         
     | 
| 
      
 154 
     | 
    
         
            +
                # @param [Array<String>] sourcekitten_output Output of sourcekitten command for each module
         
     | 
| 
       152 
155 
     | 
    
         
             
                # @param [Config] options Build options
         
     | 
| 
       153 
     | 
    
         
            -
                # @return [SourceModule] the documented source module
         
     | 
| 
       154 
156 
     | 
    
         
             
                def self.build_docs_for_sourcekitten_output(sourcekitten_output, options)
         
     | 
| 
       155 
157 
     | 
    
         
             
                  (docs, stats) = SourceKitten.parse(
         
     | 
| 
       156 
158 
     | 
    
         
             
                    sourcekitten_output,
         
     | 
| 
       157 
     | 
    
         
            -
                    options 
     | 
| 
       158 
     | 
    
         
            -
                    options.skip_undocumented,
         
     | 
| 
      
 159 
     | 
    
         
            +
                    options,
         
     | 
| 
       159 
160 
     | 
    
         
             
                    DocumentationGenerator.source_docs,
         
     | 
| 
       160 
161 
     | 
    
         
             
                  )
         
     | 
| 
       161 
162 
     | 
    
         | 
| 
         @@ -249,7 +250,8 @@ module Jazzy 
     | 
|
| 
       249 
250 
     | 
    
         
             
                    doc[:doc_coverage] = source_module.doc_coverage unless
         
     | 
| 
       250 
251 
     | 
    
         
             
                      Config.instance.hide_documentation_coverage
         
     | 
| 
       251 
252 
     | 
    
         
             
                    doc[:structure] = source_module.doc_structure
         
     | 
| 
       252 
     | 
    
         
            -
                    doc[: 
     | 
| 
      
 253 
     | 
    
         
            +
                    doc[:readme_title] = source_module.readme_title
         
     | 
| 
      
 254 
     | 
    
         
            +
                    doc[:module_name] = doc[:readme_title]
         
     | 
| 
       253 
255 
     | 
    
         
             
                    doc[:author_name] = source_module.author_name
         
     | 
| 
       254 
256 
     | 
    
         
             
                    if source_host = source_module.host
         
     | 
| 
       255 
257 
     | 
    
         
             
                      doc[:source_host_name] = source_host.name
         
     | 
| 
         @@ -259,7 +261,8 @@ module Jazzy 
     | 
|
| 
       259 
261 
     | 
    
         
             
                      doc[:github_url] = doc[:source_host_url]
         
     | 
| 
       260 
262 
     | 
    
         
             
                      doc[:github_token_url] = doc[:source_host_item_url]
         
     | 
| 
       261 
263 
     | 
    
         
             
                    end
         
     | 
| 
       262 
     | 
    
         
            -
                    doc[:dash_url] = source_module. 
     | 
| 
      
 264 
     | 
    
         
            +
                    doc[:dash_url] = source_module.dash_feed_url
         
     | 
| 
      
 265 
     | 
    
         
            +
                    doc[:breadcrumbs] = make_breadcrumbs(doc_model)
         
     | 
| 
       263 
266 
     | 
    
         
             
                  end
         
     | 
| 
       264 
267 
     | 
    
         
             
                end
         
     | 
| 
       265 
268 
     | 
    
         | 
| 
         @@ -269,7 +272,7 @@ module Jazzy 
     | 
|
| 
       269 
272 
     | 
    
         
             
                # @param [String] path_to_root
         
     | 
| 
       270 
273 
     | 
    
         
             
                def self.document_markdown(source_module, doc_model, path_to_root)
         
     | 
| 
       271 
274 
     | 
    
         
             
                  doc = new_document(source_module, doc_model)
         
     | 
| 
       272 
     | 
    
         
            -
                  name = doc_model. 
     | 
| 
      
 275 
     | 
    
         
            +
                  name = doc_model.readme? ? source_module.readme_title : doc_model.name
         
     | 
| 
       273 
276 
     | 
    
         
             
                  doc[:name] = name
         
     | 
| 
       274 
277 
     | 
    
         
             
                  doc[:overview] = render(doc_model, doc_model.content(source_module))
         
     | 
| 
       275 
278 
     | 
    
         
             
                  doc[:path_to_root] = path_to_root
         
     | 
| 
         @@ -447,5 +450,28 @@ module Jazzy 
     | 
|
| 
       447 
450 
     | 
    
         
             
                  doc.render.gsub(ELIDED_AUTOLINK_TOKEN, path_to_root)
         
     | 
| 
       448 
451 
     | 
    
         
             
                end
         
     | 
| 
       449 
452 
     | 
    
         
             
                # rubocop:enable Metrics/MethodLength
         
     | 
| 
      
 453 
     | 
    
         
            +
             
     | 
| 
      
 454 
     | 
    
         
            +
                # Breadcrumbs for a page - doesn't include the top 'readme' crumb
         
     | 
| 
      
 455 
     | 
    
         
            +
                def self.make_breadcrumbs(doc_model)
         
     | 
| 
      
 456 
     | 
    
         
            +
                  return [] if doc_model.readme?
         
     | 
| 
      
 457 
     | 
    
         
            +
             
     | 
| 
      
 458 
     | 
    
         
            +
                  docs_path = doc_model.docs_path
         
     | 
| 
      
 459 
     | 
    
         
            +
                  breadcrumbs = docs_path.map do |doc|
         
     | 
| 
      
 460 
     | 
    
         
            +
                    {
         
     | 
| 
      
 461 
     | 
    
         
            +
                      name: doc.name,
         
     | 
| 
      
 462 
     | 
    
         
            +
                      url: doc.url,
         
     | 
| 
      
 463 
     | 
    
         
            +
                      last: doc == doc_model,
         
     | 
| 
      
 464 
     | 
    
         
            +
                    }
         
     | 
| 
      
 465 
     | 
    
         
            +
                  end
         
     | 
| 
      
 466 
     | 
    
         
            +
             
     | 
| 
      
 467 
     | 
    
         
            +
                  return breadcrumbs if breadcrumbs.count == 1
         
     | 
| 
      
 468 
     | 
    
         
            +
             
     | 
| 
      
 469 
     | 
    
         
            +
                  # Add the module name to the outer type if not clear from context
         
     | 
| 
      
 470 
     | 
    
         
            +
                  if docs_path[1].ambiguous_module_name?(docs_path[0].name)
         
     | 
| 
      
 471 
     | 
    
         
            +
                    breadcrumbs[1][:name] = docs_path[1].fully_qualified_module_name
         
     | 
| 
      
 472 
     | 
    
         
            +
                  end
         
     | 
| 
      
 473 
     | 
    
         
            +
             
     | 
| 
      
 474 
     | 
    
         
            +
                  breadcrumbs
         
     | 
| 
      
 475 
     | 
    
         
            +
                end
         
     | 
| 
       450 
476 
     | 
    
         
             
              end
         
     | 
| 
       451 
477 
     | 
    
         
             
            end
         
     | 
| 
         @@ -0,0 +1,185 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # frozen_string_literal: true
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            module Jazzy
         
     | 
| 
      
 4 
     | 
    
         
            +
              # This class stores an index of symbol names for doing name lookup
         
     | 
| 
      
 5 
     | 
    
         
            +
              # when resolving custom categories and autolinks.
         
     | 
| 
      
 6 
     | 
    
         
            +
              class DocIndex
         
     | 
| 
      
 7 
     | 
    
         
            +
                # A node in the index tree.  The root has no decl; its children are
         
     | 
| 
      
 8 
     | 
    
         
            +
                # per-module indexed by module names.  The second level, where each
         
     | 
| 
      
 9 
     | 
    
         
            +
                # scope is a module, also has no decl; its children are scopes, one
         
     | 
| 
      
 10 
     | 
    
         
            +
                # for each top-level decl in the module.  From the third level onwards
         
     | 
| 
      
 11 
     | 
    
         
            +
                # the decl is valid.
         
     | 
| 
      
 12 
     | 
    
         
            +
                class Scope
         
     | 
| 
      
 13 
     | 
    
         
            +
                  attr_reader :decl # SourceDeclaration
         
     | 
| 
      
 14 
     | 
    
         
            +
                  attr_reader :children # String:Scope
         
     | 
| 
      
 15 
     | 
    
         
            +
             
     | 
| 
      
 16 
     | 
    
         
            +
                  def initialize(decl, children)
         
     | 
| 
      
 17 
     | 
    
         
            +
                    @decl = decl
         
     | 
| 
      
 18 
     | 
    
         
            +
                    @children = children
         
     | 
| 
      
 19 
     | 
    
         
            +
                  end
         
     | 
| 
      
 20 
     | 
    
         
            +
             
     | 
| 
      
 21 
     | 
    
         
            +
                  def self.new_root(module_decls)
         
     | 
| 
      
 22 
     | 
    
         
            +
                    new(nil,
         
     | 
| 
      
 23 
     | 
    
         
            +
                        module_decls.transform_values do |decls|
         
     | 
| 
      
 24 
     | 
    
         
            +
                          Scope.new_decl(nil, decls)
         
     | 
| 
      
 25 
     | 
    
         
            +
                        end)
         
     | 
| 
      
 26 
     | 
    
         
            +
                  end
         
     | 
| 
      
 27 
     | 
    
         
            +
             
     | 
| 
      
 28 
     | 
    
         
            +
                  # Decl names in a scope are usually unique.  The exceptions
         
     | 
| 
      
 29 
     | 
    
         
            +
                  # are (1) methods and (2) typealias+extension, which historically
         
     | 
| 
      
 30 
     | 
    
         
            +
                  # jazzy does not merge.  The logic here and in `merge()` below
         
     | 
| 
      
 31 
     | 
    
         
            +
                  # preserves the historical ambiguity-resolution of (1) and tries
         
     | 
| 
      
 32 
     | 
    
         
            +
                  # to do the best for (2).
         
     | 
| 
      
 33 
     | 
    
         
            +
                  def self.new_decl(decl, child_decls)
         
     | 
| 
      
 34 
     | 
    
         
            +
                    child_scopes = {}
         
     | 
| 
      
 35 
     | 
    
         
            +
                    child_decls.flat_map do |child_decl|
         
     | 
| 
      
 36 
     | 
    
         
            +
                      child_scope = Scope.new_decl(child_decl, child_decl.children)
         
     | 
| 
      
 37 
     | 
    
         
            +
                      child_decl.index_names.map do |name|
         
     | 
| 
      
 38 
     | 
    
         
            +
                        if curr = child_scopes[name]
         
     | 
| 
      
 39 
     | 
    
         
            +
                          curr.merge(child_scope)
         
     | 
| 
      
 40 
     | 
    
         
            +
                        else
         
     | 
| 
      
 41 
     | 
    
         
            +
                          child_scopes[name] = child_scope
         
     | 
| 
      
 42 
     | 
    
         
            +
                        end
         
     | 
| 
      
 43 
     | 
    
         
            +
                      end
         
     | 
| 
      
 44 
     | 
    
         
            +
                    end
         
     | 
| 
      
 45 
     | 
    
         
            +
                    new(decl, child_scopes)
         
     | 
| 
      
 46 
     | 
    
         
            +
                  end
         
     | 
| 
      
 47 
     | 
    
         
            +
             
     | 
| 
      
 48 
     | 
    
         
            +
                  def merge(new_scope)
         
     | 
| 
      
 49 
     | 
    
         
            +
                    return unless type = decl&.type
         
     | 
| 
      
 50 
     | 
    
         
            +
                    return unless new_type = new_scope.decl&.type
         
     | 
| 
      
 51 
     | 
    
         
            +
             
     | 
| 
      
 52 
     | 
    
         
            +
                    if type.swift_typealias? && new_type.swift_extension?
         
     | 
| 
      
 53 
     | 
    
         
            +
                      @children = new_scope.children
         
     | 
| 
      
 54 
     | 
    
         
            +
                    elsif type.swift_extension? && new_type.swift_typealias?
         
     | 
| 
      
 55 
     | 
    
         
            +
                      @decl = new_scope.decl
         
     | 
| 
      
 56 
     | 
    
         
            +
                    end
         
     | 
| 
      
 57 
     | 
    
         
            +
                  end
         
     | 
| 
      
 58 
     | 
    
         
            +
             
     | 
| 
      
 59 
     | 
    
         
            +
                  # Lookup of a name like `Mod.Type.method(arg:)` requires passing
         
     | 
| 
      
 60 
     | 
    
         
            +
                  # an array of name 'parts' eg. ['Mod', 'Type', 'method(arg:)'].
         
     | 
| 
      
 61 
     | 
    
         
            +
                  def lookup(parts)
         
     | 
| 
      
 62 
     | 
    
         
            +
                    return decl if parts.empty?
         
     | 
| 
      
 63 
     | 
    
         
            +
             
     | 
| 
      
 64 
     | 
    
         
            +
                    children[parts.first]&.lookup(parts[1...])
         
     | 
| 
      
 65 
     | 
    
         
            +
                  end
         
     | 
| 
      
 66 
     | 
    
         
            +
             
     | 
| 
      
 67 
     | 
    
         
            +
                  # Get an array of scopes matching the name parts.
         
     | 
| 
      
 68 
     | 
    
         
            +
                  def lookup_path(parts)
         
     | 
| 
      
 69 
     | 
    
         
            +
                    [self] +
         
     | 
| 
      
 70 
     | 
    
         
            +
                      (children[parts.first]&.lookup_path(parts[1...]) || [])
         
     | 
| 
      
 71 
     | 
    
         
            +
                  end
         
     | 
| 
      
 72 
     | 
    
         
            +
                end
         
     | 
| 
      
 73 
     | 
    
         
            +
             
     | 
| 
      
 74 
     | 
    
         
            +
                attr_reader :root_scope
         
     | 
| 
      
 75 
     | 
    
         
            +
             
     | 
| 
      
 76 
     | 
    
         
            +
                def initialize(all_decls)
         
     | 
| 
      
 77 
     | 
    
         
            +
                  @root_scope = Scope.new_root(all_decls.group_by(&:module_name))
         
     | 
| 
      
 78 
     | 
    
         
            +
                end
         
     | 
| 
      
 79 
     | 
    
         
            +
             
     | 
| 
      
 80 
     | 
    
         
            +
                # Look up a name and return the matching SourceDeclaration or nil.
         
     | 
| 
      
 81 
     | 
    
         
            +
                #
         
     | 
| 
      
 82 
     | 
    
         
            +
                # `context` is an optional SourceDeclaration indicating where the text
         
     | 
| 
      
 83 
     | 
    
         
            +
                # was found, affects name resolution - see `lookup_context()` below.
         
     | 
| 
      
 84 
     | 
    
         
            +
                def lookup(name, context = nil)
         
     | 
| 
      
 85 
     | 
    
         
            +
                  lookup_name = LookupName.new(name)
         
     | 
| 
      
 86 
     | 
    
         
            +
             
     | 
| 
      
 87 
     | 
    
         
            +
                  return lookup_fully_qualified(lookup_name) if lookup_name.fully_qualified?
         
     | 
| 
      
 88 
     | 
    
         
            +
                  return lookup_guess(lookup_name) if context.nil?
         
     | 
| 
      
 89 
     | 
    
         
            +
             
     | 
| 
      
 90 
     | 
    
         
            +
                  lookup_context(lookup_name, context)
         
     | 
| 
      
 91 
     | 
    
         
            +
                end
         
     | 
| 
      
 92 
     | 
    
         
            +
             
     | 
| 
      
 93 
     | 
    
         
            +
                private
         
     | 
| 
      
 94 
     | 
    
         
            +
             
     | 
| 
      
 95 
     | 
    
         
            +
                # Look up a fully-qualified name, ie. it starts with the module name.
         
     | 
| 
      
 96 
     | 
    
         
            +
                def lookup_fully_qualified(lookup_name)
         
     | 
| 
      
 97 
     | 
    
         
            +
                  root_scope.lookup(lookup_name.parts)
         
     | 
| 
      
 98 
     | 
    
         
            +
                end
         
     | 
| 
      
 99 
     | 
    
         
            +
             
     | 
| 
      
 100 
     | 
    
         
            +
                # Look up a top-level name best-effort, searching for a module that
         
     | 
| 
      
 101 
     | 
    
         
            +
                # has it before trying the first name-part as a module name.
         
     | 
| 
      
 102 
     | 
    
         
            +
                def lookup_guess(lookup_name)
         
     | 
| 
      
 103 
     | 
    
         
            +
                  root_scope.children.each_value do |module_scope|
         
     | 
| 
      
 104 
     | 
    
         
            +
                    if result = module_scope.lookup(lookup_name.parts)
         
     | 
| 
      
 105 
     | 
    
         
            +
                      return result
         
     | 
| 
      
 106 
     | 
    
         
            +
                    end
         
     | 
| 
      
 107 
     | 
    
         
            +
                  end
         
     | 
| 
      
 108 
     | 
    
         
            +
             
     | 
| 
      
 109 
     | 
    
         
            +
                  lookup_fully_qualified(lookup_name)
         
     | 
| 
      
 110 
     | 
    
         
            +
                end
         
     | 
| 
      
 111 
     | 
    
         
            +
             
     | 
| 
      
 112 
     | 
    
         
            +
                # Look up a name from a declaration context, approximately how
         
     | 
| 
      
 113 
     | 
    
         
            +
                # Swift resolves names.
         
     | 
| 
      
 114 
     | 
    
         
            +
                #
         
     | 
| 
      
 115 
     | 
    
         
            +
                # 1 - try and resolve with a common prefix, eg. 'B' from 'T.A'
         
     | 
| 
      
 116 
     | 
    
         
            +
                #     can match 'T.B', or 'R' from 'S.T.A' can match 'S.R'.
         
     | 
| 
      
 117 
     | 
    
         
            +
                # 2 - try and resolve as a top-level symbol from a different module
         
     | 
| 
      
 118 
     | 
    
         
            +
                # 3 - (affordance for docs writers) resolve as a child of the context,
         
     | 
| 
      
 119 
     | 
    
         
            +
                #     eg. 'B' from 'T.A' can match 'T.A.B' *only if* (1,2) fail.
         
     | 
| 
      
 120 
     | 
    
         
            +
                #     Currently disabled for Swift for back-compatibility.
         
     | 
| 
      
 121 
     | 
    
         
            +
                def lookup_context(lookup_name, context)
         
     | 
| 
      
 122 
     | 
    
         
            +
                  context_scope_path =
         
     | 
| 
      
 123 
     | 
    
         
            +
                    root_scope.lookup_path(context.fully_qualified_module_name_parts)
         
     | 
| 
      
 124 
     | 
    
         
            +
             
     | 
| 
      
 125 
     | 
    
         
            +
                  context_scope = context_scope_path.pop
         
     | 
| 
      
 126 
     | 
    
         
            +
                  context_scope_path.reverse.each do |scope|
         
     | 
| 
      
 127 
     | 
    
         
            +
                    if decl = scope.lookup(lookup_name.parts)
         
     | 
| 
      
 128 
     | 
    
         
            +
                      return decl
         
     | 
| 
      
 129 
     | 
    
         
            +
                    end
         
     | 
| 
      
 130 
     | 
    
         
            +
                  end
         
     | 
| 
      
 131 
     | 
    
         
            +
             
     | 
| 
      
 132 
     | 
    
         
            +
                  lookup_guess(lookup_name) ||
         
     | 
| 
      
 133 
     | 
    
         
            +
                    (lookup_name.objc? && context_scope.lookup(lookup_name.parts))
         
     | 
| 
      
 134 
     | 
    
         
            +
                end
         
     | 
| 
      
 135 
     | 
    
         
            +
             
     | 
| 
      
 136 
     | 
    
         
            +
                # Helper for name lookup, really a cache for information as we
         
     | 
| 
      
 137 
     | 
    
         
            +
                # try various strategies.
         
     | 
| 
      
 138 
     | 
    
         
            +
                class LookupName
         
     | 
| 
      
 139 
     | 
    
         
            +
                  attr_reader :name
         
     | 
| 
      
 140 
     | 
    
         
            +
             
     | 
| 
      
 141 
     | 
    
         
            +
                  def initialize(name)
         
     | 
| 
      
 142 
     | 
    
         
            +
                    @name = name
         
     | 
| 
      
 143 
     | 
    
         
            +
                  end
         
     | 
| 
      
 144 
     | 
    
         
            +
             
     | 
| 
      
 145 
     | 
    
         
            +
                  def fully_qualified?
         
     | 
| 
      
 146 
     | 
    
         
            +
                    name.start_with?('/')
         
     | 
| 
      
 147 
     | 
    
         
            +
                  end
         
     | 
| 
      
 148 
     | 
    
         
            +
             
     | 
| 
      
 149 
     | 
    
         
            +
                  def objc?
         
     | 
| 
      
 150 
     | 
    
         
            +
                    name.start_with?('-', '+')
         
     | 
| 
      
 151 
     | 
    
         
            +
                  end
         
     | 
| 
      
 152 
     | 
    
         
            +
             
     | 
| 
      
 153 
     | 
    
         
            +
                  def parts
         
     | 
| 
      
 154 
     | 
    
         
            +
                    @parts ||= find_parts
         
     | 
| 
      
 155 
     | 
    
         
            +
                  end
         
     | 
| 
      
 156 
     | 
    
         
            +
             
     | 
| 
      
 157 
     | 
    
         
            +
                  private
         
     | 
| 
      
 158 
     | 
    
         
            +
             
     | 
| 
      
 159 
     | 
    
         
            +
                  # Turn a name as written into a list of components to
         
     | 
| 
      
 160 
     | 
    
         
            +
                  # be matched.
         
     | 
| 
      
 161 
     | 
    
         
            +
                  # Swift: Strip out odd characters and split
         
     | 
| 
      
 162 
     | 
    
         
            +
                  # ObjC: Compound names look like '+[Class(Category) method:]'
         
     | 
| 
      
 163 
     | 
    
         
            +
                  #       and need to become ['Class(Category)', '+method:']
         
     | 
| 
      
 164 
     | 
    
         
            +
                  def find_parts
         
     | 
| 
      
 165 
     | 
    
         
            +
                    if name =~ /([+-])\[(\w+(?: ?\(\w+\))?) ([\w:]+)\]/
         
     | 
| 
      
 166 
     | 
    
         
            +
                      [Regexp.last_match[2],
         
     | 
| 
      
 167 
     | 
    
         
            +
                       Regexp.last_match[1] + Regexp.last_match[3]]
         
     | 
| 
      
 168 
     | 
    
         
            +
                    else
         
     | 
| 
      
 169 
     | 
    
         
            +
                      name
         
     | 
| 
      
 170 
     | 
    
         
            +
                        .sub(%r{^[@\/]}, '') # ignore custom attribute reference, fully-qualified
         
     | 
| 
      
 171 
     | 
    
         
            +
                        .gsub(/<.*?>/, '') # remove generic parameters
         
     | 
| 
      
 172 
     | 
    
         
            +
                        .split(%r{(?<!\.)[/.](?!\.)}) # dot or slash, but not '...'
         
     | 
| 
      
 173 
     | 
    
         
            +
                        .reject(&:empty?)
         
     | 
| 
      
 174 
     | 
    
         
            +
                    end
         
     | 
| 
      
 175 
     | 
    
         
            +
                  end
         
     | 
| 
      
 176 
     | 
    
         
            +
                end
         
     | 
| 
      
 177 
     | 
    
         
            +
              end
         
     | 
| 
      
 178 
     | 
    
         
            +
             
     | 
| 
      
 179 
     | 
    
         
            +
              class SourceDeclaration
         
     | 
| 
      
 180 
     | 
    
         
            +
                # Names for a symbol.  Permits function parameters to be omitted.
         
     | 
| 
      
 181 
     | 
    
         
            +
                def index_names
         
     | 
| 
      
 182 
     | 
    
         
            +
                  [name, name.sub(/\(.*\)/, '(...)')].uniq
         
     | 
| 
      
 183 
     | 
    
         
            +
                end
         
     | 
| 
      
 184 
     | 
    
         
            +
              end
         
     | 
| 
      
 185 
     | 
    
         
            +
            end
         
     |