├── images ├── logo.jpg ├── math.png ├── logo.sketch ├── realm.png └── screenshot.jpg ├── bin ├── sourcekitten └── jazzy ├── lib ├── jazzy │ ├── extensions │ │ ├── github │ │ │ └── img │ │ │ │ └── gh.png │ │ ├── katex │ │ │ └── css │ │ │ │ └── fonts │ │ │ │ ├── KaTeX_AMS-Regular.ttf │ │ │ │ ├── KaTeX_Main-Bold.ttf │ │ │ │ ├── KaTeX_Main-Bold.woff │ │ │ │ ├── KaTeX_Main-Bold.woff2 │ │ │ │ ├── KaTeX_Main-Italic.ttf │ │ │ │ ├── KaTeX_Math-Italic.ttf │ │ │ │ ├── KaTeX_AMS-Regular.woff │ │ │ │ ├── KaTeX_AMS-Regular.woff2 │ │ │ │ ├── KaTeX_Fraktur-Bold.ttf │ │ │ │ ├── KaTeX_Fraktur-Bold.woff │ │ │ │ ├── KaTeX_Main-Italic.woff │ │ │ │ ├── KaTeX_Main-Italic.woff2 │ │ │ │ ├── KaTeX_Main-Regular.ttf │ │ │ │ ├── KaTeX_Main-Regular.woff │ │ │ │ ├── KaTeX_Math-Italic.woff │ │ │ │ ├── KaTeX_Math-Italic.woff2 │ │ │ │ ├── KaTeX_Size1-Regular.ttf │ │ │ │ ├── KaTeX_Size2-Regular.ttf │ │ │ │ ├── KaTeX_Size3-Regular.ttf │ │ │ │ ├── KaTeX_Size4-Regular.ttf │ │ │ │ ├── KaTeX_Caligraphic-Bold.ttf │ │ │ │ ├── KaTeX_Fraktur-Bold.woff2 │ │ │ │ ├── KaTeX_Fraktur-Regular.ttf │ │ │ │ ├── KaTeX_Fraktur-Regular.woff │ │ │ │ ├── KaTeX_Main-BoldItalic.ttf │ │ │ │ ├── KaTeX_Main-BoldItalic.woff │ │ │ │ ├── KaTeX_Main-Regular.woff2 │ │ │ │ ├── KaTeX_Math-BoldItalic.ttf │ │ │ │ ├── KaTeX_Math-BoldItalic.woff │ │ │ │ ├── KaTeX_SansSerif-Bold.ttf │ │ │ │ ├── KaTeX_SansSerif-Bold.woff │ │ │ │ ├── KaTeX_SansSerif-Bold.woff2 │ │ │ │ ├── KaTeX_SansSerif-Italic.ttf │ │ │ │ ├── KaTeX_Script-Regular.ttf │ │ │ │ ├── KaTeX_Script-Regular.woff │ │ │ │ ├── KaTeX_Script-Regular.woff2 │ │ │ │ ├── KaTeX_Size1-Regular.woff │ │ │ │ ├── KaTeX_Size1-Regular.woff2 │ │ │ │ ├── KaTeX_Size2-Regular.woff │ │ │ │ ├── KaTeX_Size2-Regular.woff2 │ │ │ │ ├── KaTeX_Size3-Regular.woff │ │ │ │ ├── KaTeX_Size3-Regular.woff2 │ │ │ │ ├── KaTeX_Size4-Regular.woff │ │ │ │ ├── KaTeX_Size4-Regular.woff2 │ │ │ │ ├── KaTeX_Caligraphic-Bold.woff │ │ │ │ ├── KaTeX_Caligraphic-Bold.woff2 │ │ │ │ ├── KaTeX_Fraktur-Regular.woff2 │ │ │ │ ├── KaTeX_Main-BoldItalic.woff2 │ │ │ │ ├── KaTeX_Math-BoldItalic.woff2 │ │ │ │ ├── KaTeX_SansSerif-Italic.woff │ │ │ │ ├── KaTeX_SansSerif-Italic.woff2 │ │ │ │ ├── KaTeX_SansSerif-Regular.ttf │ │ │ │ ├── KaTeX_SansSerif-Regular.woff │ │ │ │ ├── KaTeX_Typewriter-Regular.ttf │ │ │ │ ├── KaTeX_Caligraphic-Regular.ttf │ │ │ │ ├── KaTeX_Caligraphic-Regular.woff │ │ │ │ ├── KaTeX_Caligraphic-Regular.woff2 │ │ │ │ ├── KaTeX_SansSerif-Regular.woff2 │ │ │ │ ├── KaTeX_Typewriter-Regular.woff │ │ │ │ └── KaTeX_Typewriter-Regular.woff2 │ │ ├── bitbucket │ │ │ └── img │ │ │ │ └── bitbucket.svg │ │ └── gitlab │ │ │ └── img │ │ │ └── gitlab.svg │ ├── themes │ │ ├── apple │ │ │ ├── assets │ │ │ │ ├── img │ │ │ │ │ ├── carat.png │ │ │ │ │ ├── dash.png │ │ │ │ │ └── spinner.gif │ │ │ │ ├── js │ │ │ │ │ ├── jazzy.js │ │ │ │ │ └── jazzy.search.js │ │ │ │ └── css │ │ │ │ │ └── highlight.css.scss │ │ │ └── templates │ │ │ │ ├── tasks.mustache │ │ │ │ ├── parameter.mustache │ │ │ │ ├── footer.mustache │ │ │ │ ├── deprecation.mustache │ │ │ │ ├── nav.mustache │ │ │ │ ├── header.mustache │ │ │ │ ├── task.mustache │ │ │ │ └── doc.mustache │ │ ├── jony │ │ │ ├── assets │ │ │ │ ├── img │ │ │ │ │ ├── carat.png │ │ │ │ │ └── dash.png │ │ │ │ ├── js │ │ │ │ │ └── jazzy.js │ │ │ │ └── css │ │ │ │ │ └── highlight.css.scss │ │ │ └── templates │ │ │ │ ├── tasks.mustache │ │ │ │ ├── parameter.mustache │ │ │ │ ├── footer.mustache │ │ │ │ ├── deprecation.mustache │ │ │ │ ├── nav.mustache │ │ │ │ ├── header.mustache │ │ │ │ ├── task.mustache │ │ │ │ └── doc.mustache │ │ └── fullwidth │ │ │ ├── assets │ │ │ ├── img │ │ │ │ ├── carat.png │ │ │ │ ├── dash.png │ │ │ │ └── spinner.gif │ │ │ ├── js │ │ │ │ ├── jazzy.js │ │ │ │ └── jazzy.search.js │ │ │ └── css │ │ │ │ └── highlight.css.scss │ │ │ └── templates │ │ │ ├── parameter.mustache │ │ │ ├── tasks.mustache │ │ │ ├── footer.mustache │ │ │ ├── deprecation.mustache │ │ │ ├── nav.mustache │ │ │ ├── header.mustache │ │ │ ├── task.mustache │ │ │ └── doc.mustache │ ├── gem_version.rb │ ├── search_builder.rb │ ├── highlighter.rb │ ├── documentation_generator.rb │ ├── docset_builder │ │ └── info_plist.mustache │ ├── symbol_graph │ │ ├── ext_key.rb │ │ ├── relationship.rb │ │ ├── constraint.rb │ │ ├── ext_node.rb │ │ ├── sym_node.rb │ │ ├── graph.rb │ │ └── symbol.rb │ ├── executable.rb │ ├── source_module.rb │ ├── source_mark.rb │ ├── doc.rb │ ├── stats.rb │ ├── source_document.rb │ ├── source_host.rb │ ├── source_declaration │ │ └── access_control_level.rb │ ├── symbol_graph.rb │ ├── docset_builder.rb │ ├── grouper.rb │ ├── podspec_documenter.rb │ ├── doc_index.rb │ ├── source_declaration.rb │ └── jazzy_markdown.rb └── jazzy.rb ├── .gitmodules ├── js ├── package.json └── package-lock.json ├── Gemfile ├── spec ├── spec_helper │ └── pre_flight.rb ├── spec_helper.rb └── Moya.podspec ├── LICENSE ├── .gitignore ├── Dangerfile ├── .github └── workflows │ └── Tests.yml ├── jazzy.gemspec ├── CONTRIBUTING.md ├── Rakefile ├── Gemfile.lock └── ObjectiveC.md /images/logo.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/realm/jazzy/HEAD/images/logo.jpg -------------------------------------------------------------------------------- /images/math.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/realm/jazzy/HEAD/images/math.png -------------------------------------------------------------------------------- /bin/sourcekitten: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/realm/jazzy/HEAD/bin/sourcekitten -------------------------------------------------------------------------------- /images/logo.sketch: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/realm/jazzy/HEAD/images/logo.sketch -------------------------------------------------------------------------------- /images/realm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/realm/jazzy/HEAD/images/realm.png -------------------------------------------------------------------------------- /images/screenshot.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/realm/jazzy/HEAD/images/screenshot.jpg -------------------------------------------------------------------------------- /lib/jazzy/extensions/github/img/gh.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/realm/jazzy/HEAD/lib/jazzy/extensions/github/img/gh.png -------------------------------------------------------------------------------- /lib/jazzy/themes/apple/assets/img/carat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/realm/jazzy/HEAD/lib/jazzy/themes/apple/assets/img/carat.png -------------------------------------------------------------------------------- /lib/jazzy/themes/apple/assets/img/dash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/realm/jazzy/HEAD/lib/jazzy/themes/apple/assets/img/dash.png -------------------------------------------------------------------------------- /lib/jazzy/themes/jony/assets/img/carat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/realm/jazzy/HEAD/lib/jazzy/themes/jony/assets/img/carat.png -------------------------------------------------------------------------------- /lib/jazzy/themes/jony/assets/img/dash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/realm/jazzy/HEAD/lib/jazzy/themes/jony/assets/img/dash.png -------------------------------------------------------------------------------- /lib/jazzy/themes/apple/assets/img/spinner.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/realm/jazzy/HEAD/lib/jazzy/themes/apple/assets/img/spinner.gif -------------------------------------------------------------------------------- /lib/jazzy/themes/fullwidth/assets/img/carat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/realm/jazzy/HEAD/lib/jazzy/themes/fullwidth/assets/img/carat.png -------------------------------------------------------------------------------- /lib/jazzy/themes/fullwidth/assets/img/dash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/realm/jazzy/HEAD/lib/jazzy/themes/fullwidth/assets/img/dash.png -------------------------------------------------------------------------------- /lib/jazzy/gem_version.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Jazzy 4 | VERSION = '0.15.4' unless defined? Jazzy::VERSION 5 | end 6 | -------------------------------------------------------------------------------- /lib/jazzy/themes/fullwidth/assets/img/spinner.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/realm/jazzy/HEAD/lib/jazzy/themes/fullwidth/assets/img/spinner.gif -------------------------------------------------------------------------------- /lib/jazzy.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'jazzy/config' 4 | require 'jazzy/doc_builder' 5 | 6 | Encoding.default_external = Encoding::UTF_8 7 | -------------------------------------------------------------------------------- /lib/jazzy/extensions/katex/css/fonts/KaTeX_AMS-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/realm/jazzy/HEAD/lib/jazzy/extensions/katex/css/fonts/KaTeX_AMS-Regular.ttf -------------------------------------------------------------------------------- /lib/jazzy/extensions/katex/css/fonts/KaTeX_Main-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/realm/jazzy/HEAD/lib/jazzy/extensions/katex/css/fonts/KaTeX_Main-Bold.ttf -------------------------------------------------------------------------------- /lib/jazzy/extensions/katex/css/fonts/KaTeX_Main-Bold.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/realm/jazzy/HEAD/lib/jazzy/extensions/katex/css/fonts/KaTeX_Main-Bold.woff -------------------------------------------------------------------------------- /lib/jazzy/extensions/katex/css/fonts/KaTeX_Main-Bold.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/realm/jazzy/HEAD/lib/jazzy/extensions/katex/css/fonts/KaTeX_Main-Bold.woff2 -------------------------------------------------------------------------------- /lib/jazzy/extensions/katex/css/fonts/KaTeX_Main-Italic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/realm/jazzy/HEAD/lib/jazzy/extensions/katex/css/fonts/KaTeX_Main-Italic.ttf -------------------------------------------------------------------------------- /lib/jazzy/extensions/katex/css/fonts/KaTeX_Math-Italic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/realm/jazzy/HEAD/lib/jazzy/extensions/katex/css/fonts/KaTeX_Math-Italic.ttf -------------------------------------------------------------------------------- /lib/jazzy/extensions/katex/css/fonts/KaTeX_AMS-Regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/realm/jazzy/HEAD/lib/jazzy/extensions/katex/css/fonts/KaTeX_AMS-Regular.woff -------------------------------------------------------------------------------- /lib/jazzy/extensions/katex/css/fonts/KaTeX_AMS-Regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/realm/jazzy/HEAD/lib/jazzy/extensions/katex/css/fonts/KaTeX_AMS-Regular.woff2 -------------------------------------------------------------------------------- /lib/jazzy/extensions/katex/css/fonts/KaTeX_Fraktur-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/realm/jazzy/HEAD/lib/jazzy/extensions/katex/css/fonts/KaTeX_Fraktur-Bold.ttf -------------------------------------------------------------------------------- /lib/jazzy/extensions/katex/css/fonts/KaTeX_Fraktur-Bold.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/realm/jazzy/HEAD/lib/jazzy/extensions/katex/css/fonts/KaTeX_Fraktur-Bold.woff -------------------------------------------------------------------------------- /lib/jazzy/extensions/katex/css/fonts/KaTeX_Main-Italic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/realm/jazzy/HEAD/lib/jazzy/extensions/katex/css/fonts/KaTeX_Main-Italic.woff -------------------------------------------------------------------------------- /lib/jazzy/extensions/katex/css/fonts/KaTeX_Main-Italic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/realm/jazzy/HEAD/lib/jazzy/extensions/katex/css/fonts/KaTeX_Main-Italic.woff2 -------------------------------------------------------------------------------- /lib/jazzy/extensions/katex/css/fonts/KaTeX_Main-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/realm/jazzy/HEAD/lib/jazzy/extensions/katex/css/fonts/KaTeX_Main-Regular.ttf -------------------------------------------------------------------------------- /lib/jazzy/extensions/katex/css/fonts/KaTeX_Main-Regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/realm/jazzy/HEAD/lib/jazzy/extensions/katex/css/fonts/KaTeX_Main-Regular.woff -------------------------------------------------------------------------------- /lib/jazzy/extensions/katex/css/fonts/KaTeX_Math-Italic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/realm/jazzy/HEAD/lib/jazzy/extensions/katex/css/fonts/KaTeX_Math-Italic.woff -------------------------------------------------------------------------------- /lib/jazzy/extensions/katex/css/fonts/KaTeX_Math-Italic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/realm/jazzy/HEAD/lib/jazzy/extensions/katex/css/fonts/KaTeX_Math-Italic.woff2 -------------------------------------------------------------------------------- /lib/jazzy/extensions/katex/css/fonts/KaTeX_Size1-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/realm/jazzy/HEAD/lib/jazzy/extensions/katex/css/fonts/KaTeX_Size1-Regular.ttf -------------------------------------------------------------------------------- /lib/jazzy/extensions/katex/css/fonts/KaTeX_Size2-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/realm/jazzy/HEAD/lib/jazzy/extensions/katex/css/fonts/KaTeX_Size2-Regular.ttf -------------------------------------------------------------------------------- /lib/jazzy/extensions/katex/css/fonts/KaTeX_Size3-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/realm/jazzy/HEAD/lib/jazzy/extensions/katex/css/fonts/KaTeX_Size3-Regular.ttf -------------------------------------------------------------------------------- /lib/jazzy/extensions/katex/css/fonts/KaTeX_Size4-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/realm/jazzy/HEAD/lib/jazzy/extensions/katex/css/fonts/KaTeX_Size4-Regular.ttf -------------------------------------------------------------------------------- /lib/jazzy/extensions/katex/css/fonts/KaTeX_Caligraphic-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/realm/jazzy/HEAD/lib/jazzy/extensions/katex/css/fonts/KaTeX_Caligraphic-Bold.ttf -------------------------------------------------------------------------------- /lib/jazzy/extensions/katex/css/fonts/KaTeX_Fraktur-Bold.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/realm/jazzy/HEAD/lib/jazzy/extensions/katex/css/fonts/KaTeX_Fraktur-Bold.woff2 -------------------------------------------------------------------------------- /lib/jazzy/extensions/katex/css/fonts/KaTeX_Fraktur-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/realm/jazzy/HEAD/lib/jazzy/extensions/katex/css/fonts/KaTeX_Fraktur-Regular.ttf -------------------------------------------------------------------------------- /lib/jazzy/extensions/katex/css/fonts/KaTeX_Fraktur-Regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/realm/jazzy/HEAD/lib/jazzy/extensions/katex/css/fonts/KaTeX_Fraktur-Regular.woff -------------------------------------------------------------------------------- /lib/jazzy/extensions/katex/css/fonts/KaTeX_Main-BoldItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/realm/jazzy/HEAD/lib/jazzy/extensions/katex/css/fonts/KaTeX_Main-BoldItalic.ttf -------------------------------------------------------------------------------- /lib/jazzy/extensions/katex/css/fonts/KaTeX_Main-BoldItalic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/realm/jazzy/HEAD/lib/jazzy/extensions/katex/css/fonts/KaTeX_Main-BoldItalic.woff -------------------------------------------------------------------------------- /lib/jazzy/extensions/katex/css/fonts/KaTeX_Main-Regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/realm/jazzy/HEAD/lib/jazzy/extensions/katex/css/fonts/KaTeX_Main-Regular.woff2 -------------------------------------------------------------------------------- /lib/jazzy/extensions/katex/css/fonts/KaTeX_Math-BoldItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/realm/jazzy/HEAD/lib/jazzy/extensions/katex/css/fonts/KaTeX_Math-BoldItalic.ttf -------------------------------------------------------------------------------- /lib/jazzy/extensions/katex/css/fonts/KaTeX_Math-BoldItalic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/realm/jazzy/HEAD/lib/jazzy/extensions/katex/css/fonts/KaTeX_Math-BoldItalic.woff -------------------------------------------------------------------------------- /lib/jazzy/extensions/katex/css/fonts/KaTeX_SansSerif-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/realm/jazzy/HEAD/lib/jazzy/extensions/katex/css/fonts/KaTeX_SansSerif-Bold.ttf -------------------------------------------------------------------------------- /lib/jazzy/extensions/katex/css/fonts/KaTeX_SansSerif-Bold.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/realm/jazzy/HEAD/lib/jazzy/extensions/katex/css/fonts/KaTeX_SansSerif-Bold.woff -------------------------------------------------------------------------------- /lib/jazzy/extensions/katex/css/fonts/KaTeX_SansSerif-Bold.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/realm/jazzy/HEAD/lib/jazzy/extensions/katex/css/fonts/KaTeX_SansSerif-Bold.woff2 -------------------------------------------------------------------------------- /lib/jazzy/extensions/katex/css/fonts/KaTeX_SansSerif-Italic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/realm/jazzy/HEAD/lib/jazzy/extensions/katex/css/fonts/KaTeX_SansSerif-Italic.ttf -------------------------------------------------------------------------------- /lib/jazzy/extensions/katex/css/fonts/KaTeX_Script-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/realm/jazzy/HEAD/lib/jazzy/extensions/katex/css/fonts/KaTeX_Script-Regular.ttf -------------------------------------------------------------------------------- /lib/jazzy/extensions/katex/css/fonts/KaTeX_Script-Regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/realm/jazzy/HEAD/lib/jazzy/extensions/katex/css/fonts/KaTeX_Script-Regular.woff -------------------------------------------------------------------------------- /lib/jazzy/extensions/katex/css/fonts/KaTeX_Script-Regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/realm/jazzy/HEAD/lib/jazzy/extensions/katex/css/fonts/KaTeX_Script-Regular.woff2 -------------------------------------------------------------------------------- /lib/jazzy/extensions/katex/css/fonts/KaTeX_Size1-Regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/realm/jazzy/HEAD/lib/jazzy/extensions/katex/css/fonts/KaTeX_Size1-Regular.woff -------------------------------------------------------------------------------- /lib/jazzy/extensions/katex/css/fonts/KaTeX_Size1-Regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/realm/jazzy/HEAD/lib/jazzy/extensions/katex/css/fonts/KaTeX_Size1-Regular.woff2 -------------------------------------------------------------------------------- /lib/jazzy/extensions/katex/css/fonts/KaTeX_Size2-Regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/realm/jazzy/HEAD/lib/jazzy/extensions/katex/css/fonts/KaTeX_Size2-Regular.woff -------------------------------------------------------------------------------- /lib/jazzy/extensions/katex/css/fonts/KaTeX_Size2-Regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/realm/jazzy/HEAD/lib/jazzy/extensions/katex/css/fonts/KaTeX_Size2-Regular.woff2 -------------------------------------------------------------------------------- /lib/jazzy/extensions/katex/css/fonts/KaTeX_Size3-Regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/realm/jazzy/HEAD/lib/jazzy/extensions/katex/css/fonts/KaTeX_Size3-Regular.woff -------------------------------------------------------------------------------- /lib/jazzy/extensions/katex/css/fonts/KaTeX_Size3-Regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/realm/jazzy/HEAD/lib/jazzy/extensions/katex/css/fonts/KaTeX_Size3-Regular.woff2 -------------------------------------------------------------------------------- /lib/jazzy/extensions/katex/css/fonts/KaTeX_Size4-Regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/realm/jazzy/HEAD/lib/jazzy/extensions/katex/css/fonts/KaTeX_Size4-Regular.woff -------------------------------------------------------------------------------- /lib/jazzy/extensions/katex/css/fonts/KaTeX_Size4-Regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/realm/jazzy/HEAD/lib/jazzy/extensions/katex/css/fonts/KaTeX_Size4-Regular.woff2 -------------------------------------------------------------------------------- /lib/jazzy/extensions/katex/css/fonts/KaTeX_Caligraphic-Bold.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/realm/jazzy/HEAD/lib/jazzy/extensions/katex/css/fonts/KaTeX_Caligraphic-Bold.woff -------------------------------------------------------------------------------- /lib/jazzy/extensions/katex/css/fonts/KaTeX_Caligraphic-Bold.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/realm/jazzy/HEAD/lib/jazzy/extensions/katex/css/fonts/KaTeX_Caligraphic-Bold.woff2 -------------------------------------------------------------------------------- /lib/jazzy/extensions/katex/css/fonts/KaTeX_Fraktur-Regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/realm/jazzy/HEAD/lib/jazzy/extensions/katex/css/fonts/KaTeX_Fraktur-Regular.woff2 -------------------------------------------------------------------------------- /lib/jazzy/extensions/katex/css/fonts/KaTeX_Main-BoldItalic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/realm/jazzy/HEAD/lib/jazzy/extensions/katex/css/fonts/KaTeX_Main-BoldItalic.woff2 -------------------------------------------------------------------------------- /lib/jazzy/extensions/katex/css/fonts/KaTeX_Math-BoldItalic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/realm/jazzy/HEAD/lib/jazzy/extensions/katex/css/fonts/KaTeX_Math-BoldItalic.woff2 -------------------------------------------------------------------------------- /lib/jazzy/extensions/katex/css/fonts/KaTeX_SansSerif-Italic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/realm/jazzy/HEAD/lib/jazzy/extensions/katex/css/fonts/KaTeX_SansSerif-Italic.woff -------------------------------------------------------------------------------- /lib/jazzy/extensions/katex/css/fonts/KaTeX_SansSerif-Italic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/realm/jazzy/HEAD/lib/jazzy/extensions/katex/css/fonts/KaTeX_SansSerif-Italic.woff2 -------------------------------------------------------------------------------- /lib/jazzy/extensions/katex/css/fonts/KaTeX_SansSerif-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/realm/jazzy/HEAD/lib/jazzy/extensions/katex/css/fonts/KaTeX_SansSerif-Regular.ttf -------------------------------------------------------------------------------- /lib/jazzy/extensions/katex/css/fonts/KaTeX_SansSerif-Regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/realm/jazzy/HEAD/lib/jazzy/extensions/katex/css/fonts/KaTeX_SansSerif-Regular.woff -------------------------------------------------------------------------------- /lib/jazzy/extensions/katex/css/fonts/KaTeX_Typewriter-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/realm/jazzy/HEAD/lib/jazzy/extensions/katex/css/fonts/KaTeX_Typewriter-Regular.ttf -------------------------------------------------------------------------------- /lib/jazzy/extensions/katex/css/fonts/KaTeX_Caligraphic-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/realm/jazzy/HEAD/lib/jazzy/extensions/katex/css/fonts/KaTeX_Caligraphic-Regular.ttf -------------------------------------------------------------------------------- /lib/jazzy/extensions/katex/css/fonts/KaTeX_Caligraphic-Regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/realm/jazzy/HEAD/lib/jazzy/extensions/katex/css/fonts/KaTeX_Caligraphic-Regular.woff -------------------------------------------------------------------------------- /lib/jazzy/extensions/katex/css/fonts/KaTeX_Caligraphic-Regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/realm/jazzy/HEAD/lib/jazzy/extensions/katex/css/fonts/KaTeX_Caligraphic-Regular.woff2 -------------------------------------------------------------------------------- /lib/jazzy/extensions/katex/css/fonts/KaTeX_SansSerif-Regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/realm/jazzy/HEAD/lib/jazzy/extensions/katex/css/fonts/KaTeX_SansSerif-Regular.woff2 -------------------------------------------------------------------------------- /lib/jazzy/extensions/katex/css/fonts/KaTeX_Typewriter-Regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/realm/jazzy/HEAD/lib/jazzy/extensions/katex/css/fonts/KaTeX_Typewriter-Regular.woff -------------------------------------------------------------------------------- /lib/jazzy/extensions/katex/css/fonts/KaTeX_Typewriter-Regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/realm/jazzy/HEAD/lib/jazzy/extensions/katex/css/fonts/KaTeX_Typewriter-Regular.woff2 -------------------------------------------------------------------------------- /lib/jazzy/themes/jony/templates/tasks.mustache: -------------------------------------------------------------------------------- 1 | {{#tasks.count}} 2 |
3 | {{#tasks}} 4 | {{> task}} 5 | {{/tasks}} 6 |
7 | {{/tasks.count}} 8 | -------------------------------------------------------------------------------- /lib/jazzy/themes/apple/templates/tasks.mustache: -------------------------------------------------------------------------------- 1 | {{#tasks.count}} 2 |
3 | {{#tasks}} 4 | {{> task}} 5 | {{/tasks}} 6 |
7 | {{/tasks.count}} 8 | -------------------------------------------------------------------------------- /lib/jazzy/themes/apple/templates/parameter.mustache: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | {{name}} 5 | 6 | 7 | 8 |
9 | {{{discussion}}} 10 |
11 | 12 | 13 | -------------------------------------------------------------------------------- /lib/jazzy/themes/jony/templates/parameter.mustache: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | {{name}} 5 | 6 | 7 | 8 |
9 | {{{discussion}}} 10 |
11 | 12 | 13 | -------------------------------------------------------------------------------- /lib/jazzy/themes/fullwidth/templates/parameter.mustache: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | {{name}} 5 | 6 | 7 | 8 |
9 | {{{discussion}}} 10 |
11 | 12 | 13 | -------------------------------------------------------------------------------- /lib/jazzy/themes/fullwidth/templates/tasks.mustache: -------------------------------------------------------------------------------- 1 | {{#tasks.count}} 2 |
3 |
4 | {{#tasks}} 5 | {{> task}} 6 | {{/tasks}} 7 |
8 |
9 | {{/tasks.count}} 10 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "SourceKitten"] 2 | path = SourceKitten 3 | url = https://github.com/jpsim/SourceKitten.git 4 | [submodule "spec/integration_specs"] 5 | path = spec/integration_specs 6 | url = https://github.com/realm/jazzy-integration-specs.git 7 | -------------------------------------------------------------------------------- /lib/jazzy/themes/apple/templates/footer.mustache: -------------------------------------------------------------------------------- 1 | 5 | -------------------------------------------------------------------------------- /lib/jazzy/themes/jony/templates/footer.mustache: -------------------------------------------------------------------------------- 1 | 5 | -------------------------------------------------------------------------------- /lib/jazzy/themes/fullwidth/templates/footer.mustache: -------------------------------------------------------------------------------- 1 | 5 | -------------------------------------------------------------------------------- /lib/jazzy/themes/apple/templates/deprecation.mustache: -------------------------------------------------------------------------------- 1 | {{#deprecation_message}} 2 |
3 |

Deprecated

4 | {{{deprecation_message}}} 5 |
6 | {{/deprecation_message}} 7 | {{#unavailable_message}} 8 |
9 |

Unavailable

10 | {{{unavailable_message}}} 11 |
12 | {{/unavailable_message}} 13 | -------------------------------------------------------------------------------- /lib/jazzy/themes/jony/templates/deprecation.mustache: -------------------------------------------------------------------------------- 1 | {{#deprecation_message}} 2 |
3 |

Deprecated

4 | {{{deprecation_message}}} 5 |
6 | {{/deprecation_message}} 7 | {{#unavailable_message}} 8 |
9 |

Unavailable

10 | {{{unavailable_message}}} 11 |
12 | {{/unavailable_message}} 13 | -------------------------------------------------------------------------------- /lib/jazzy/themes/fullwidth/templates/deprecation.mustache: -------------------------------------------------------------------------------- 1 | {{#deprecation_message}} 2 |
3 |

Deprecated

4 | {{{deprecation_message}}} 5 |
6 | {{/deprecation_message}} 7 | {{#unavailable_message}} 8 |
9 |

Unavailable

10 | {{{unavailable_message}}} 11 |
12 | {{/unavailable_message}} 13 | -------------------------------------------------------------------------------- /js/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "jazzy-js", 3 | "version": "1.0.0", 4 | "description": "Jazzy theme dependencies", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "author": "", 10 | "license": "MIT", 11 | "dependencies": { 12 | "corejs-typeahead": "^1.3.1", 13 | "jquery": "^3.7.1", 14 | "katex": "^0.16.8", 15 | "lunr": "^2.3.9" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | source 'https://rubygems.org' 4 | 5 | gemspec 6 | 7 | group :development do 8 | # Code style 9 | gem 'rubocop', '~> 1.18' 10 | 11 | # Tests 12 | gem 'bacon' 13 | gem 'mocha' 14 | gem 'mocha-on-bacon' 15 | gem 'prettybacon' 16 | gem 'webmock' 17 | 18 | # Integration tests 19 | gem 'clintegracon', git: 'https://github.com/mrackwitz/CLIntegracon.git' 20 | gem 'diffy' 21 | 22 | # Code Review 23 | gem 'danger' 24 | end 25 | -------------------------------------------------------------------------------- /lib/jazzy/themes/apple/templates/nav.mustache: -------------------------------------------------------------------------------- 1 | 17 | -------------------------------------------------------------------------------- /bin/jazzy: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # frozen_string_literal: true 3 | 4 | if $PROGRAM_NAME == __FILE__ && !ENV['JAZZY_NO_BUNDLER'] 5 | ENV['BUNDLE_GEMFILE'] = File.expand_path('../Gemfile', __dir__) 6 | require 'rubygems' 7 | require 'bundler/setup' 8 | $LOAD_PATH.unshift File.expand_path('../lib', __dir__) 9 | elsif ENV['JAZZY_NO_BUNDLER'] 10 | require 'rubygems' 11 | gem 'jazzy' 12 | end 13 | 14 | require 'jazzy' 15 | 16 | Jazzy::DocBuilder.build(Jazzy::Config.instance = Jazzy::Config.parse!) 17 | -------------------------------------------------------------------------------- /lib/jazzy/themes/jony/templates/nav.mustache: -------------------------------------------------------------------------------- 1 | 17 | -------------------------------------------------------------------------------- /lib/jazzy/themes/fullwidth/templates/nav.mustache: -------------------------------------------------------------------------------- 1 | 17 | -------------------------------------------------------------------------------- /lib/jazzy/search_builder.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Jazzy 4 | module SearchBuilder 5 | def self.build(source_module, output_dir) 6 | decls = source_module.all_declarations.select do |d| 7 | d.type && d.name && !d.name.empty? 8 | end 9 | index = decls.to_h do |d| 10 | [d.url, 11 | { 12 | name: d.name, 13 | abstract: d.abstract && d.abstract.split("\n").map(&:strip).first, 14 | parent_name: d.parent_in_code&.name, 15 | }.reject { |_, v| v.nil? || v.empty? }] 16 | end 17 | File.write(File.join(output_dir, 'search.json'), index.to_json) 18 | end 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /spec/spec_helper/pre_flight.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # Restores the config to the default state before each requirement 4 | 5 | module Bacon 6 | class Context 7 | old_run_requirement = instance_method(:run_requirement) 8 | 9 | define_method(:run_requirement) do |description, spec| 10 | temporary_directory = SpecHelper.temporary_directory 11 | 12 | ::Jazzy::Config.instance = nil 13 | ::Jazzy::Config.instance.tap do |c| 14 | c.source_directory = temporary_directory 15 | end 16 | 17 | temporary_directory.rmtree if temporary_directory.exist? 18 | temporary_directory.mkpath 19 | 20 | old_run_requirement.bind(self).call(description, spec) 21 | end 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'rubygems' 4 | require 'bundler/setup' 5 | require 'bacon' 6 | require 'mocha-on-bacon' 7 | require 'pretty_bacon' 8 | require 'pathname' 9 | 10 | ROOT = Pathname.new(File.expand_path('..', __dir__)) 11 | $LOAD_PATH.unshift((ROOT + 'lib').to_s) 12 | $LOAD_PATH.unshift((ROOT + 'spec').to_s) 13 | 14 | require 'jazzy' 15 | 16 | require 'spec_helper/pre_flight' 17 | 18 | Bacon.summary_at_exit 19 | 20 | module Bacon 21 | class Context 22 | include Jazzy::Config::Mixin 23 | 24 | def temporary_directory 25 | SpecHelper.temporary_directory 26 | end 27 | end 28 | end 29 | 30 | Mocha::Configuration.prevent(:stubbing_non_existent_method) 31 | 32 | module SpecHelper 33 | def self.temporary_directory 34 | ROOT + 'tmp' 35 | end 36 | end 37 | -------------------------------------------------------------------------------- /lib/jazzy/themes/jony/templates/header.mustache: -------------------------------------------------------------------------------- 1 |
2 |
3 |

4 | {{docs_title}} 5 | {{#doc_coverage}} ({{doc_coverage}}% documented){{/doc_coverage}} 6 |

7 | 8 | {{#source_host_url}} 9 |

10 | 11 | {{source_host_name}} 12 | View on {{source_host_name}} 13 | 14 |

15 | {{/source_host_url}} 16 | 17 | {{#dash_url}} 18 |

19 | 20 | Dash 21 | Install in Dash 22 | 23 |

24 | {{/dash_url}} 25 |
26 |
27 | -------------------------------------------------------------------------------- /lib/jazzy/highlighter.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'rouge' 4 | 5 | module Jazzy 6 | # This module helps highlight code 7 | module Highlighter 8 | SWIFT = 'swift' 9 | OBJC = 'objective_c' 10 | 11 | class Formatter < Rouge::Formatters::HTML 12 | def initialize(language) 13 | @language = language 14 | super() 15 | end 16 | 17 | def stream(tokens, &block) 18 | yield "
"
19 |         super
20 |         yield "
\n" 21 | end 22 | end 23 | 24 | def self.highlight_swift(source) 25 | highlight(source, SWIFT) 26 | end 27 | 28 | def self.highlight_objc(source) 29 | highlight(source, OBJC) 30 | end 31 | 32 | def self.highlight(source, language) 33 | source && Rouge.highlight(source, language, Formatter.new(language)) 34 | end 35 | end 36 | end 37 | -------------------------------------------------------------------------------- /lib/jazzy/themes/apple/templates/header.mustache: -------------------------------------------------------------------------------- 1 |
2 |
3 |

{{docs_title}}{{#doc_coverage}} ({{doc_coverage}}% documented){{/doc_coverage}}

4 | {{#source_host_url}} 5 |

{{source_host_name}}View on {{source_host_name}}

6 | {{/source_host_url}} 7 | {{#dash_url}} 8 |

DashInstall in Dash

9 | {{/dash_url}} 10 | {{^disable_search}} 11 |
12 |
13 | 14 |
15 |
16 | {{/disable_search}} 17 |
18 |
19 | -------------------------------------------------------------------------------- /lib/jazzy/extensions/bitbucket/img/bitbucket.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /lib/jazzy/documentation_generator.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'pathname' 4 | 5 | require 'jazzy/jazzy_markdown' 6 | require 'jazzy/source_document' 7 | 8 | module Jazzy 9 | module DocumentationGenerator 10 | extend Config::Mixin 11 | 12 | def self.source_docs 13 | documentation_entries.map do |file_path| 14 | SourceDocument.new.tap do |sd| 15 | sd.name = File.basename(file_path, '.md') 16 | sd.overview = overview Pathname(file_path) 17 | sd.usr = "documentation.#{sd.name}" 18 | end 19 | end 20 | end 21 | 22 | def self.overview(file_path) 23 | return '' unless file_path&.exist? 24 | 25 | file_path.read 26 | end 27 | 28 | def self.documentation_entries 29 | return [] unless 30 | config.documentation_glob_configured && config.documentation_glob 31 | 32 | config.documentation_glob.select { |e| File.file? e } 33 | end 34 | end 35 | end 36 | -------------------------------------------------------------------------------- /lib/jazzy/docset_builder/info_plist.mustache: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleIdentifier 6 | com.jazzy.{{lowercase_safe_name}} 7 | CFBundleName 8 | {{name}} 9 | DocSetPlatformFamily 10 | {{lowercase_name}} 11 | isDashDocset 12 | 13 | dashIndexFilePath 14 | index.html 15 | isJavaScriptEnabled 16 | 17 | DashDocSetFamily 18 | dashtoc 19 | {{#root_url}} 20 | DashDocSetFallbackURL 21 | {{{.}}} 22 | {{/root_url}} 23 | {{#playground_url}} 24 | DashDocSetPlayURL 25 | {{{.}}} 26 | {{/playground_url}} 27 | 28 | 29 | -------------------------------------------------------------------------------- /lib/jazzy/symbol_graph/ext_key.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Jazzy 4 | module SymbolGraph 5 | # An ExtKey identifies an extension of a type, made up of the USR of 6 | # the type and the constraints of the extension. With Swift 5.9 extension 7 | # symbols, the USR is the 'fake' USR invented by symbolgraph to solve the 8 | # same problem as this type, which means less merging takes place. 9 | class ExtKey 10 | attr_accessor :usr 11 | attr_accessor :constraints_text 12 | 13 | def initialize(usr, constraints) 14 | self.usr = usr 15 | self.constraints_text = constraints.map(&:to_swift).join 16 | end 17 | 18 | def hash_key 19 | usr + constraints_text 20 | end 21 | 22 | def eql?(other) 23 | hash_key == other.hash_key 24 | end 25 | 26 | def hash 27 | hash_key.hash 28 | end 29 | end 30 | 31 | class ExtSymNode 32 | def ext_key 33 | ExtKey.new(usr, all_constraints.ext) 34 | end 35 | end 36 | end 37 | end 38 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Realm Inc. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | jazzy-docs/ 2 | /docs/ 3 | node_modules 4 | 5 | *.gem 6 | *.rbc 7 | /.config 8 | /coverage/ 9 | /InstalledFiles 10 | /pkg/ 11 | /spec/reports/ 12 | /test/tmp/ 13 | /test/version_tmp/ 14 | /tmp/ 15 | 16 | ## Specific to RubyMotion: 17 | .dat* 18 | .repl_history 19 | build/ 20 | 21 | ## Documentation cache and generated files: 22 | /.yardoc/ 23 | /_yardoc/ 24 | /doc/ 25 | /rdoc/ 26 | 27 | ## Environment normalisation: 28 | /.bundle/ 29 | /lib/bundler/man/ 30 | 31 | # for a library or gem, you might want to ignore these files since the code is 32 | # intended to run in multiple environments; otherwise, check them in: 33 | # Gemfile.lock 34 | # .ruby-version 35 | # .ruby-gemset 36 | 37 | # unless supporting rvm < 1.11.0 or doing something fancy, ignore this: 38 | .rvmrc 39 | 40 | .DS_Store 41 | .AppleDouble 42 | .LSOverride 43 | 44 | # Icon must end with two \r 45 | Icon 46 | 47 | 48 | # Thumbnails 49 | ._* 50 | 51 | # Files that might appear on external disk 52 | .Spotlight-V100 53 | .Trashes 54 | 55 | # Directories potentially created on remote AFP share 56 | .AppleDB 57 | .AppleDesktop 58 | Network Trash Folder 59 | Temporary Items 60 | .apdisk 61 | vendor 62 | -------------------------------------------------------------------------------- /lib/jazzy/themes/fullwidth/templates/header.mustache: -------------------------------------------------------------------------------- 1 |
2 |

3 | 4 | {{docs_title}} 5 | 6 | {{#doc_coverage}} ({{doc_coverage}}% documented){{/doc_coverage}} 7 |

8 | 9 | {{^disable_search}} 10 |
11 |
12 | 13 |
14 |
15 | {{/disable_search}} 16 | 17 | {{#source_host_url}} 18 |

19 | 20 | {{source_host_name}} 21 | View on {{source_host_name}} 22 | 23 |

24 | {{/source_host_url}} 25 | 26 | {{#dash_url}} 27 |

28 | 29 | Dash 30 | Install in Dash 31 | 32 |

33 | {{/dash_url}} 34 |
35 | -------------------------------------------------------------------------------- /Dangerfile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # Warn when there is a big PR 4 | warn('Big PR') if git.lines_of_code > 500 5 | 6 | # Don't let testing shortcuts get into master by accident 7 | (git.modified_files + git.added_files - %w[Dangerfile]).each do |file| 8 | next unless File.file?(file) 9 | 10 | contents = File.read(file) 11 | if file.start_with?('spec') 12 | failure("`xit` or `fit` left in tests (#{file})") if contents =~ /^\w*[xf]it/ 13 | failure("`fdescribe` left in tests (#{file})") if contents =~ /^\w*fdescribe/ 14 | end 15 | end 16 | 17 | # Sometimes its a README fix, or something like that - which isn't relevant for 18 | # including in a CHANGELOG for example 19 | has_app_changes = !git.modified_files.grep(/lib/).empty? 20 | has_test_changes = !git.modified_files.grep(/spec/).empty? 21 | 22 | # Add a CHANGELOG entry for app changes 23 | if !git.modified_files.include?('CHANGELOG.md') && has_app_changes 24 | failure "Please include a CHANGELOG entry. \nYou can find it at [CHANGELOG.md](https://github.com/realm/jazzy/blob/master/CHANGELOG.md)." 25 | message 'Note, we hard-wrap at 80 chars and use 2 spaces after the last line.' 26 | end 27 | 28 | # Non-trivial amounts of app changes without tests 29 | if git.lines_of_code > 50 && has_app_changes && !has_test_changes 30 | warn 'This PR may need tests.' 31 | end 32 | -------------------------------------------------------------------------------- /lib/jazzy/executable.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Jazzy 4 | module Executable 5 | class IO < Array 6 | def initialize(io = nil) 7 | super() 8 | @io = io 9 | end 10 | 11 | def <<(value) 12 | super 13 | ensure 14 | @io << value.to_s if @io 15 | end 16 | 17 | def to_s 18 | join("\n") 19 | end 20 | end 21 | 22 | class << self 23 | def execute_command(executable, args, raise_on_failure, env: {}) 24 | require 'shellwords' 25 | bin = `which #{executable.to_s.shellescape}`.strip 26 | raise "Unable to locate the executable `#{executable}`" if bin.empty? 27 | 28 | require 'open4' 29 | 30 | stdout = IO.new 31 | stderr = IO.new($stderr) 32 | 33 | options = { stdout: stdout, stderr: stderr, status: true } 34 | status = Open4.spawn(env, bin, *args, options) 35 | unless status.success? 36 | full_command = "#{bin.shellescape} #{args.map(&:shellescape)}" 37 | output = stdout.to_s << stderr.to_s 38 | if raise_on_failure 39 | raise "#{full_command}\n\n#{output}" 40 | else 41 | warn("[!] Failed: #{full_command}") 42 | end 43 | end 44 | [stdout.to_s, status] 45 | end 46 | end 47 | end 48 | end 49 | -------------------------------------------------------------------------------- /lib/jazzy/source_module.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'uri' 4 | 5 | require 'jazzy/config' 6 | require 'jazzy/source_declaration' 7 | require 'jazzy/source_host' 8 | 9 | module Jazzy 10 | # A cache of info that is common across all page templating, gathered 11 | # from other parts of the program. 12 | class SourceModule 13 | include Config::Mixin 14 | 15 | attr_accessor :readme_title 16 | attr_accessor :docs 17 | attr_accessor :doc_coverage 18 | attr_accessor :doc_structure 19 | attr_accessor :author_name 20 | attr_accessor :author_url 21 | attr_accessor :dash_feed_url 22 | attr_accessor :host 23 | 24 | def initialize(docs, doc_structure, doc_coverage, docset_builder) 25 | self.docs = docs 26 | self.doc_structure = doc_structure 27 | self.doc_coverage = doc_coverage 28 | title = config.readme_title || config.module_names.first 29 | self.readme_title = title.empty? ? 'Index' : title 30 | self.author_name = config.author_name 31 | self.author_url = config.author_url 32 | self.host = SourceHost.create(config) 33 | self.dash_feed_url = docset_builder.dash_feed_url 34 | end 35 | 36 | def all_declarations 37 | all_declarations = [] 38 | visitor = lambda do |d| 39 | all_declarations.unshift(*d) 40 | d.map(&:children).each { |c| visitor[c] } 41 | end 42 | visitor[docs] 43 | all_declarations.reject { |doc| doc.name == 'index' } 44 | end 45 | end 46 | end 47 | -------------------------------------------------------------------------------- /.github/workflows/Tests.yml: -------------------------------------------------------------------------------- 1 | name: Tests 2 | 3 | on: 4 | push: 5 | branches: [master] 6 | pull_request: 7 | branches: ['*'] 8 | 9 | jobs: 10 | danger_and_rubocop: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v4 14 | - uses: ruby/setup-ruby@v1 15 | with: 16 | ruby-version: 3.3 17 | bundler-cache: true 18 | - name: Rubocop 19 | run: | 20 | bundle exec rake rubocop 21 | - name: Danger 22 | env: 23 | DANGER_GITHUB_API_TOKEN: ${{ secrets.GITHUB_TOKEN }} 24 | run: | 25 | bundle exec danger --verbose 26 | 27 | spec: 28 | runs-on: macos-15 29 | continue-on-error: true 30 | strategy: 31 | matrix: 32 | spec: ["objc_spec", "swift_spec", "cocoapods_spec"] 33 | steps: 34 | - uses: actions/checkout@v4 35 | with: 36 | submodules: recursive 37 | persist-credentials: false 38 | - uses: maxim-lobanov/setup-xcode@v1 39 | with: 40 | xcode-version: '26.1.0-beta' 41 | - uses: ruby/setup-ruby@v1 42 | with: 43 | ruby-version: 3.3 44 | bundler-cache: true 45 | - name: Cache cocoapods 46 | uses: actions/cache@v4 47 | env: 48 | cache-name: cocoapods 49 | with: 50 | path: ~/.cocoapods 51 | key: ${{ matrix.spec }}-${{ env.cache-name }} 52 | - name: Test 53 | run: | 54 | bundle exec rake ${{ matrix.spec }} 55 | -------------------------------------------------------------------------------- /lib/jazzy/source_mark.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Jazzy 4 | class SourceMark 5 | attr_accessor :name 6 | attr_accessor :has_start_dash 7 | attr_accessor :has_end_dash 8 | 9 | def initialize(mark_string = nil) 10 | return unless mark_string 11 | 12 | # Format: 'MARK: - NAME -' with dashes optional 13 | mark_content = mark_string.sub(/^MARK: /, '') 14 | 15 | if mark_content.empty? 16 | # Empty 17 | return 18 | elsif mark_content == '-' 19 | # Separator 20 | self.has_start_dash = true 21 | return 22 | end 23 | 24 | self.has_start_dash = mark_content.start_with?('- ') 25 | self.has_end_dash = mark_content.end_with?(' -') 26 | 27 | start_index = has_start_dash ? 2 : 0 28 | end_index = has_end_dash ? -3 : -1 29 | 30 | self.name = mark_content[start_index..end_index] 31 | end 32 | 33 | def self.new_generic_requirements(requirements) 34 | marked_up = requirements.gsub(/\b([^=:]\S*)\b/, '`\1`') 35 | text = "Available where #{marked_up}" 36 | new(text) 37 | end 38 | 39 | def empty? 40 | !name && !has_start_dash && !has_end_dash 41 | end 42 | 43 | def copy(other) 44 | self.name = other.name 45 | self.has_start_dash = other.has_start_dash 46 | self.has_end_dash = other.has_end_dash 47 | end 48 | 49 | # Can we merge the contents of another mark into our own? 50 | def can_merge?(other) 51 | other.empty? || other.name == name 52 | end 53 | end 54 | end 55 | -------------------------------------------------------------------------------- /jazzy.gemspec: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | # frozen_string_literal: true 3 | 4 | require File.expand_path('lib/jazzy/gem_version.rb', File.dirname(__FILE__)) 5 | 6 | Gem::Specification.new do |spec| 7 | spec.name = 'jazzy' 8 | spec.version = Jazzy::VERSION 9 | spec.authors = ['JP Simard', 'Tim Anglade', 'Samuel Giddins', 'John Fairhurst'] 10 | spec.email = ['jp@jpsim.com'] 11 | spec.summary = 'Soulful docs for Swift & Objective-C.' 12 | spec.description = 'Soulful docs for Swift & Objective-C. ' \ 13 | "Run in your SPM or Xcode project's root directory for " \ 14 | 'instant HTML docs.' 15 | spec.homepage = 'https://github.com/realm/jazzy' 16 | spec.license = 'MIT' 17 | 18 | spec.files = `git ls-files`.split($/) 19 | spec.executables << 'jazzy' 20 | 21 | spec.add_dependency 'activesupport', '>= 5.0', '< 8' 22 | spec.add_dependency 'cocoapods', '~> 1.5' 23 | spec.add_dependency 'logger' 24 | spec.add_dependency 'mustache', '~> 1.1' 25 | spec.add_dependency 'open4', '~> 1.3' 26 | spec.add_dependency 'redcarpet', '~> 3.4' 27 | spec.add_dependency 'rexml', ['>= 3.2.7', '< 4.0'] 28 | spec.add_dependency 'rouge', ['>= 2.0.6', '< 5.0'] 29 | spec.add_dependency 'sassc', '~> 2.1' 30 | spec.add_dependency 'sqlite3', '~> 1.3' 31 | spec.add_dependency 'xcinvoke', '~> 0.3.0' 32 | 33 | spec.add_development_dependency 'bundler', '~> 2.1' 34 | spec.add_development_dependency 'rake', '~> 13.0' 35 | 36 | spec.required_ruby_version = '>= 2.6.3' 37 | spec.metadata['rubygems_mfa_required'] = 'true' 38 | end 39 | -------------------------------------------------------------------------------- /lib/jazzy/doc.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'date' 4 | require 'pathname' 5 | require 'mustache' 6 | 7 | require 'jazzy/config' 8 | require 'jazzy/gem_version' 9 | require 'jazzy/jazzy_markdown' 10 | 11 | module Jazzy 12 | class Doc < Mustache 13 | include Config::Mixin 14 | 15 | self.template_name = 'doc' 16 | 17 | def copyright 18 | copyright = config.copyright || ( 19 | # Fake date is used to keep integration tests consistent 20 | date = ENV['JAZZY_FAKE_DATE'] || DateTime.now.strftime('%Y-%m-%d') 21 | year = date[0..3] 22 | "© #{year} [#{config.author_name}](#{config.author_url}). " \ 23 | "All rights reserved. (Last updated: #{date})" 24 | ) 25 | Markdown.render_copyright(copyright).chomp 26 | end 27 | 28 | def jazzy_version 29 | # Fake version is used to keep integration tests consistent 30 | ENV['JAZZY_FAKE_VERSION'] || Jazzy::VERSION 31 | end 32 | 33 | def objc_first? 34 | config.objc_mode && config.hide_declarations != 'objc' 35 | end 36 | 37 | def language_stub 38 | objc_first? ? 'objc' : 'swift' 39 | end 40 | 41 | def module_version 42 | config.version_configured ? config.version : nil 43 | end 44 | 45 | def docs_title 46 | if config.title_configured 47 | config.title 48 | elsif config.version_configured 49 | # Fake version for integration tests 50 | version = ENV['JAZZY_FAKE_MODULE_VERSION'] || config.version 51 | "#{config.module_configs.first.module_name} #{version} Docs" 52 | else 53 | "#{config.module_configs.first.module_name} Docs" 54 | end 55 | end 56 | 57 | def enable_katex 58 | Markdown.has_math 59 | end 60 | end 61 | end 62 | -------------------------------------------------------------------------------- /spec/Moya.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | s.name = "Moya" 3 | s.version = "15.0.0" 4 | s.summary = "Network abstraction layer written in Swift" 5 | s.description = <<-EOS 6 | Moya abstracts network commands using Swift Generics to provide developers 7 | with more compile-time confidence. 8 | 9 | ReactiveSwift and RxSwift extensions exist as well. Instructions for installation 10 | are in [the README](https://github.com/Moya/Moya). 11 | EOS 12 | s.homepage = "https://github.com/Moya/Moya" 13 | s.license = { :type => "MIT", :file => "License.md" } 14 | s.author = { "Ash Furrow" => "ash@ashfurrow.com" } 15 | s.social_media_url = "http://twitter.com/ashfurrow" 16 | s.ios.deployment_target = '11.0' 17 | s.osx.deployment_target = '10.13' 18 | s.tvos.deployment_target = '10.0' 19 | s.watchos.deployment_target = '3.0' 20 | s.source = { :git => "https://github.com/Moya/Moya.git", :tag => s.version } 21 | s.default_subspec = "Core" 22 | s.swift_version = '5.3' 23 | s.cocoapods_version = '>= 1.4.0' 24 | 25 | s.subspec "Core" do |ss| 26 | ss.source_files = "Sources/Moya/", "Sources/Moya/Plugins/" 27 | ss.dependency "Alamofire", "~> 5.0" 28 | ss.framework = "Foundation" 29 | s.ios.deployment_target = '11.0' 30 | s.osx.deployment_target = '10.13' 31 | s.tvos.deployment_target = '10.0' 32 | s.watchos.deployment_target = '3.0' 33 | end 34 | 35 | s.subspec "Combine" do |ss| 36 | ss.source_files = "Sources/CombineMoya/" 37 | ss.dependency "Moya/Core" 38 | ss.framework = "Combine" 39 | ss.ios.deployment_target = '13.0' 40 | ss.osx.deployment_target = '10.15' 41 | ss.tvos.deployment_target = '13.0' 42 | ss.watchos.deployment_target = '6.0' 43 | end 44 | 45 | s.subspec "ReactiveSwift" do |ss| 46 | ss.source_files = "Sources/ReactiveMoya/" 47 | ss.dependency "Moya/Core" 48 | ss.dependency "ReactiveSwift", "~> 7.0" 49 | ss.ios.deployment_target = '11.0' 50 | ss.osx.deployment_target = '10.13' 51 | ss.tvos.deployment_target = '10.0' 52 | ss.watchos.deployment_target = '3.0' 53 | end 54 | 55 | end 56 | -------------------------------------------------------------------------------- /lib/jazzy/symbol_graph/relationship.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Jazzy 4 | module SymbolGraph 5 | # A Relationship is a tidied-up SymbolGraph JSON object 6 | class Relationship 7 | attr_accessor :kind 8 | attr_accessor :source_usr 9 | attr_accessor :target_usr 10 | attr_accessor :target_fallback # can be nil 11 | attr_accessor :constraints # array, can be empty 12 | 13 | # Order matters: defaultImplementationOf after the protocols 14 | # have been defined; extensionTo after all the extensions have 15 | # been discovered. 16 | KINDS = %w[memberOf conformsTo overrides inheritsFrom 17 | requirementOf optionalRequirementOf 18 | defaultImplementationOf extensionTo].freeze 19 | 20 | KINDS_INDEX = KINDS.to_h { |i| [i.to_sym, KINDS.index(i)] }.freeze 21 | 22 | def protocol_requirement? 23 | %i[requirementOf optionalRequirementOf].include? kind 24 | end 25 | 26 | def default_implementation? 27 | kind == :defaultImplementationOf 28 | end 29 | 30 | def extension_to? 31 | kind == :extensionTo 32 | end 33 | 34 | # Protocol conformances added by compiler to actor decls that 35 | # users aren't interested in. 36 | def actor_protocol? 37 | %w[Actor AnyActor Sendable].include?(target_fallback) 38 | end 39 | 40 | def initialize(hash) 41 | kind = hash[:kind] 42 | unless KINDS.include?(kind) 43 | raise "Unknown relationship kind '#{kind}'" 44 | end 45 | 46 | self.kind = kind.to_sym 47 | self.source_usr = hash[:source] 48 | self.target_usr = hash[:target] 49 | if fallback = hash[:targetFallback] 50 | # Strip the leading module name 51 | self.target_fallback = fallback.sub(/^.*?\./, '') 52 | end 53 | self.constraints = Constraint.new_list(hash[:swiftConstraints] || []) 54 | end 55 | 56 | # Sort order 57 | include Comparable 58 | 59 | def <=>(other) 60 | return 0 if kind == other.kind 61 | 62 | KINDS_INDEX[kind] <=> KINDS_INDEX[other.kind] 63 | end 64 | end 65 | end 66 | end 67 | -------------------------------------------------------------------------------- /lib/jazzy/themes/apple/assets/js/jazzy.js: -------------------------------------------------------------------------------- 1 | // Jazzy - https://github.com/realm/jazzy 2 | // Copyright Realm Inc. 3 | // SPDX-License-Identifier: MIT 4 | 5 | window.jazzy = {'docset': false} 6 | if (typeof window.dash != 'undefined') { 7 | document.documentElement.className += ' dash' 8 | window.jazzy.docset = true 9 | } 10 | if (navigator.userAgent.match(/xcode/i)) { 11 | document.documentElement.className += ' xcode' 12 | window.jazzy.docset = true 13 | } 14 | 15 | function toggleItem($link, $content) { 16 | var animationDuration = 300; 17 | $link.toggleClass('token-open'); 18 | $content.slideToggle(animationDuration); 19 | } 20 | 21 | function itemLinkToContent($link) { 22 | return $link.parent().parent().next(); 23 | } 24 | 25 | // On doc load + hash-change, open any targeted item 26 | function openCurrentItemIfClosed() { 27 | if (window.jazzy.docset) { 28 | return; 29 | } 30 | var $link = $(`a[name="${location.hash.substring(1)}"]`).nextAll('.token'); 31 | $content = itemLinkToContent($link); 32 | if ($content.is(':hidden')) { 33 | toggleItem($link, $content); 34 | } 35 | } 36 | 37 | $(openCurrentItemIfClosed); 38 | $(window).on('hashchange', openCurrentItemIfClosed); 39 | 40 | // On item link ('token') click, toggle its discussion 41 | $('.token').on('click', function(event) { 42 | if (window.jazzy.docset) { 43 | return; 44 | } 45 | var $link = $(this); 46 | toggleItem($link, itemLinkToContent($link)); 47 | 48 | // Keeps the document from jumping to the hash. 49 | var href = $link.attr('href'); 50 | if (history.pushState) { 51 | history.pushState({}, '', href); 52 | } else { 53 | location.hash = href; 54 | } 55 | event.preventDefault(); 56 | }); 57 | 58 | // Clicks on links to the current, closed, item need to open the item 59 | $("a:not('.token')").on('click', function() { 60 | if (location == this.href) { 61 | openCurrentItemIfClosed(); 62 | } 63 | }); 64 | 65 | // KaTeX rendering 66 | if ("katex" in window) { 67 | $($('.math').each( (_, element) => { 68 | katex.render(element.textContent, element, { 69 | displayMode: $(element).hasClass('m-block'), 70 | throwOnError: false, 71 | trust: true 72 | }); 73 | })) 74 | } 75 | -------------------------------------------------------------------------------- /lib/jazzy/themes/jony/assets/js/jazzy.js: -------------------------------------------------------------------------------- 1 | // Jazzy - https://github.com/realm/jazzy 2 | // Copyright Realm Inc. 3 | // SPDX-License-Identifier: MIT 4 | 5 | window.jazzy = {'docset': false} 6 | if (typeof window.dash != 'undefined') { 7 | document.documentElement.className += ' dash' 8 | window.jazzy.docset = true 9 | } 10 | if (navigator.userAgent.match(/xcode/i)) { 11 | document.documentElement.className += ' xcode' 12 | window.jazzy.docset = true 13 | } 14 | 15 | function toggleItem($link, $content) { 16 | var animationDuration = 300; 17 | $link.toggleClass('token-open'); 18 | $content.slideToggle(animationDuration); 19 | } 20 | 21 | function itemLinkToContent($link) { 22 | return $link.parent().parent().next(); 23 | } 24 | 25 | // On doc load + hash-change, open any targeted item 26 | function openCurrentItemIfClosed() { 27 | if (window.jazzy.docset) { 28 | return; 29 | } 30 | var $link = $(`a[name="${location.hash.substring(1)}"]`).nextAll('.token'); 31 | $content = itemLinkToContent($link); 32 | if ($content.is(':hidden')) { 33 | toggleItem($link, $content); 34 | } 35 | } 36 | 37 | $(openCurrentItemIfClosed); 38 | $(window).on('hashchange', openCurrentItemIfClosed); 39 | 40 | // On item link ('token') click, toggle its discussion 41 | $('.token').on('click', function(event) { 42 | if (window.jazzy.docset) { 43 | return; 44 | } 45 | var $link = $(this); 46 | toggleItem($link, itemLinkToContent($link)); 47 | 48 | // Keeps the document from jumping to the hash. 49 | var href = $link.attr('href'); 50 | if (history.pushState) { 51 | history.pushState({}, '', href); 52 | } else { 53 | location.hash = href; 54 | } 55 | event.preventDefault(); 56 | }); 57 | 58 | // Clicks on links to the current, closed, item need to open the item 59 | $("a:not('.token')").on('click', function() { 60 | if (location == this.href) { 61 | openCurrentItemIfClosed(); 62 | } 63 | }); 64 | 65 | // KaTeX rendering 66 | if ("katex" in window) { 67 | $($('.math').each( (_, element) => { 68 | katex.render(element.textContent, element, { 69 | displayMode: $(element).hasClass('m-block'), 70 | throwOnError: false, 71 | trust: true 72 | }); 73 | })) 74 | } 75 | -------------------------------------------------------------------------------- /lib/jazzy/themes/fullwidth/assets/js/jazzy.js: -------------------------------------------------------------------------------- 1 | // Jazzy - https://github.com/realm/jazzy 2 | // Copyright Realm Inc. 3 | // SPDX-License-Identifier: MIT 4 | 5 | window.jazzy = {'docset': false} 6 | if (typeof window.dash != 'undefined') { 7 | document.documentElement.className += ' dash' 8 | window.jazzy.docset = true 9 | } 10 | if (navigator.userAgent.match(/xcode/i)) { 11 | document.documentElement.className += ' xcode' 12 | window.jazzy.docset = true 13 | } 14 | 15 | function toggleItem($link, $content) { 16 | var animationDuration = 300; 17 | $link.toggleClass('token-open'); 18 | $content.slideToggle(animationDuration); 19 | } 20 | 21 | function itemLinkToContent($link) { 22 | return $link.parent().parent().next(); 23 | } 24 | 25 | // On doc load + hash-change, open any targeted item 26 | function openCurrentItemIfClosed() { 27 | if (window.jazzy.docset) { 28 | return; 29 | } 30 | var $link = $(`a[name="${location.hash.substring(1)}"]`).nextAll('.token'); 31 | $content = itemLinkToContent($link); 32 | if ($content.is(':hidden')) { 33 | toggleItem($link, $content); 34 | } 35 | } 36 | 37 | $(openCurrentItemIfClosed); 38 | $(window).on('hashchange', openCurrentItemIfClosed); 39 | 40 | // On item link ('token') click, toggle its discussion 41 | $('.token').on('click', function(event) { 42 | if (window.jazzy.docset) { 43 | return; 44 | } 45 | var $link = $(this); 46 | toggleItem($link, itemLinkToContent($link)); 47 | 48 | // Keeps the document from jumping to the hash. 49 | var href = $link.attr('href'); 50 | if (history.pushState) { 51 | history.pushState({}, '', href); 52 | } else { 53 | location.hash = href; 54 | } 55 | event.preventDefault(); 56 | }); 57 | 58 | // Clicks on links to the current, closed, item need to open the item 59 | $("a:not('.token')").on('click', function() { 60 | if (location == this.href) { 61 | openCurrentItemIfClosed(); 62 | } 63 | }); 64 | 65 | // KaTeX rendering 66 | if ("katex" in window) { 67 | $($('.math').each( (_, element) => { 68 | katex.render(element.textContent, element, { 69 | displayMode: $(element).hasClass('m-block'), 70 | throwOnError: false, 71 | trust: true 72 | }); 73 | })) 74 | } 75 | -------------------------------------------------------------------------------- /js/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "jazzy-js", 3 | "version": "1.0.0", 4 | "lockfileVersion": 3, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "jazzy-js", 9 | "version": "1.0.0", 10 | "license": "MIT", 11 | "dependencies": { 12 | "corejs-typeahead": "^1.3.1", 13 | "jquery": "^3.7.1", 14 | "katex": "^0.16.8", 15 | "lunr": "^2.3.9" 16 | } 17 | }, 18 | "node_modules/commander": { 19 | "version": "8.3.0", 20 | "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz", 21 | "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==", 22 | "license": "MIT", 23 | "engines": { 24 | "node": ">= 12" 25 | } 26 | }, 27 | "node_modules/corejs-typeahead": { 28 | "version": "1.3.4", 29 | "resolved": "https://registry.npmjs.org/corejs-typeahead/-/corejs-typeahead-1.3.4.tgz", 30 | "integrity": "sha512-4SAc1UWszc05IyG9te1JHSeLas5WUWKmKAB8m9E6AAmHCpYhdSaySkblVSIUwQG2FR5uAsaeHbKM+pkEP7SiNw==", 31 | "license": "MIT", 32 | "dependencies": { 33 | "jquery": ">=1.11" 34 | } 35 | }, 36 | "node_modules/jquery": { 37 | "version": "3.7.1", 38 | "resolved": "https://registry.npmjs.org/jquery/-/jquery-3.7.1.tgz", 39 | "integrity": "sha512-m4avr8yL8kmFN8psrbFFFmB/If14iN5o9nw/NgnnM+kybDJpRsAynV2BsfpTYrTRysYUdADVD7CkUUizgkpLfg==", 40 | "license": "MIT" 41 | }, 42 | "node_modules/katex": { 43 | "version": "0.16.25", 44 | "resolved": "https://registry.npmjs.org/katex/-/katex-0.16.25.tgz", 45 | "integrity": "sha512-woHRUZ/iF23GBP1dkDQMh1QBad9dmr8/PAwNA54VrSOVYgI12MAcE14TqnDdQOdzyEonGzMepYnqBMYdsoAr8Q==", 46 | "funding": [ 47 | "https://opencollective.com/katex", 48 | "https://github.com/sponsors/katex" 49 | ], 50 | "license": "MIT", 51 | "dependencies": { 52 | "commander": "^8.3.0" 53 | }, 54 | "bin": { 55 | "katex": "cli.js" 56 | } 57 | }, 58 | "node_modules/lunr": { 59 | "version": "2.3.9", 60 | "resolved": "https://registry.npmjs.org/lunr/-/lunr-2.3.9.tgz", 61 | "integrity": "sha512-zTU3DaZaF3Rt9rhN3uBMGQD3dD2/vFQqnvZCDv4dl5iOzq2IZQqTxu90r4E5J+nP70J3ilqVCrbho2eWaeW8Ow==", 62 | "license": "MIT" 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /lib/jazzy/themes/apple/assets/js/jazzy.search.js: -------------------------------------------------------------------------------- 1 | // Jazzy - https://github.com/realm/jazzy 2 | // Copyright Realm Inc. 3 | // SPDX-License-Identifier: MIT 4 | 5 | $(function(){ 6 | var $typeahead = $('[data-typeahead]'); 7 | var $form = $typeahead.parents('form'); 8 | var searchURL = $form.attr('action'); 9 | 10 | function displayTemplate(result) { 11 | return result.name; 12 | } 13 | 14 | function suggestionTemplate(result) { 15 | var t = '
'; 16 | t += '' + result.name + ''; 17 | if (result.parent_name) { 18 | t += '' + result.parent_name + ''; 19 | } 20 | t += '
'; 21 | return t; 22 | } 23 | 24 | $typeahead.one('focus', function() { 25 | $form.addClass('loading'); 26 | 27 | $.getJSON(searchURL).then(function(searchData) { 28 | const searchIndex = lunr(function() { 29 | this.ref('url'); 30 | this.field('name'); 31 | this.field('abstract'); 32 | for (const [url, doc] of Object.entries(searchData)) { 33 | this.add({url: url, name: doc.name, abstract: doc.abstract}); 34 | } 35 | }); 36 | 37 | $typeahead.typeahead( 38 | { 39 | highlight: true, 40 | minLength: 3, 41 | autoselect: true 42 | }, 43 | { 44 | limit: 10, 45 | display: displayTemplate, 46 | templates: { suggestion: suggestionTemplate }, 47 | source: function(query, sync) { 48 | const lcSearch = query.toLowerCase(); 49 | const results = searchIndex.query(function(q) { 50 | q.term(lcSearch, { boost: 100 }); 51 | q.term(lcSearch, { 52 | boost: 10, 53 | wildcard: lunr.Query.wildcard.TRAILING 54 | }); 55 | }).map(function(result) { 56 | var doc = searchData[result.ref]; 57 | doc.url = result.ref; 58 | return doc; 59 | }); 60 | sync(results); 61 | } 62 | } 63 | ); 64 | $form.removeClass('loading'); 65 | $typeahead.trigger('focus'); 66 | }); 67 | }); 68 | 69 | var baseURL = searchURL.slice(0, -"search.json".length); 70 | 71 | $typeahead.on('typeahead:select', function(e, result) { 72 | window.location = baseURL + result.url; 73 | }); 74 | }); 75 | -------------------------------------------------------------------------------- /lib/jazzy/themes/fullwidth/assets/js/jazzy.search.js: -------------------------------------------------------------------------------- 1 | // Jazzy - https://github.com/realm/jazzy 2 | // Copyright Realm Inc. 3 | // SPDX-License-Identifier: MIT 4 | 5 | $(function(){ 6 | var $typeahead = $('[data-typeahead]'); 7 | var $form = $typeahead.parents('form'); 8 | var searchURL = $form.attr('action'); 9 | 10 | function displayTemplate(result) { 11 | return result.name; 12 | } 13 | 14 | function suggestionTemplate(result) { 15 | var t = '
'; 16 | t += '' + result.name + ''; 17 | if (result.parent_name) { 18 | t += '' + result.parent_name + ''; 19 | } 20 | t += '
'; 21 | return t; 22 | } 23 | 24 | $typeahead.one('focus', function() { 25 | $form.addClass('loading'); 26 | 27 | $.getJSON(searchURL).then(function(searchData) { 28 | const searchIndex = lunr(function() { 29 | this.ref('url'); 30 | this.field('name'); 31 | this.field('abstract'); 32 | for (const [url, doc] of Object.entries(searchData)) { 33 | this.add({url: url, name: doc.name, abstract: doc.abstract}); 34 | } 35 | }); 36 | 37 | $typeahead.typeahead( 38 | { 39 | highlight: true, 40 | minLength: 3, 41 | autoselect: true 42 | }, 43 | { 44 | limit: 10, 45 | display: displayTemplate, 46 | templates: { suggestion: suggestionTemplate }, 47 | source: function(query, sync) { 48 | const lcSearch = query.toLowerCase(); 49 | const results = searchIndex.query(function(q) { 50 | q.term(lcSearch, { boost: 100 }); 51 | q.term(lcSearch, { 52 | boost: 10, 53 | wildcard: lunr.Query.wildcard.TRAILING 54 | }); 55 | }).map(function(result) { 56 | var doc = searchData[result.ref]; 57 | doc.url = result.ref; 58 | return doc; 59 | }); 60 | sync(results); 61 | } 62 | } 63 | ); 64 | $form.removeClass('loading'); 65 | $typeahead.trigger('focus'); 66 | }); 67 | }); 68 | 69 | var baseURL = searchURL.slice(0, -"search.json".length); 70 | 71 | $typeahead.on('typeahead:select', function(e, result) { 72 | window.location = baseURL + result.url; 73 | }); 74 | }); 75 | -------------------------------------------------------------------------------- /lib/jazzy/stats.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Jazzy 4 | # Collect + report metadata about a processed module 5 | class Stats 6 | include Config::Mixin 7 | 8 | attr_reader :documented, :acl_skipped, :spi_skipped, :undocumented_decls 9 | 10 | def add_documented 11 | @documented += 1 12 | end 13 | 14 | def add_acl_skipped 15 | @acl_skipped += 1 16 | end 17 | 18 | def add_spi_skipped 19 | @spi_skipped += 1 20 | end 21 | 22 | def add_undocumented(decl) 23 | @undocumented_decls << decl 24 | end 25 | 26 | def remove_undocumented(decl) 27 | @undocumented_decls.delete(decl) 28 | end 29 | 30 | def acl_included 31 | documented + undocumented 32 | end 33 | 34 | def undocumented 35 | undocumented_decls.count 36 | end 37 | 38 | def initialize 39 | @documented = @acl_skipped = @spi_skipped = 0 40 | @undocumented_decls = [] 41 | end 42 | 43 | def report 44 | puts "#{doc_coverage}% documentation coverage " \ 45 | "with #{undocumented} undocumented " \ 46 | "#{symbol_or_symbols(undocumented)}" 47 | 48 | if acl_included > 0 49 | swift_acls = comma_list(config.min_acl.included_levels) 50 | puts "included #{acl_included} " + 51 | (config.objc_mode ? '' : "#{swift_acls} ") + 52 | symbol_or_symbols(acl_included) 53 | end 54 | 55 | if !config.objc_mode && acl_skipped > 0 56 | puts "skipped #{acl_skipped} " \ 57 | "#{comma_list(config.min_acl.excluded_levels)} " \ 58 | "#{symbol_or_symbols(acl_skipped)} " \ 59 | '(use `--min-acl` to specify a different minimum ACL)' 60 | end 61 | 62 | if spi_skipped > 0 63 | puts "skipped #{spi_skipped} SPI #{symbol_or_symbols(spi_skipped)} " \ 64 | '(use `--include-spi-declarations` to include these)' 65 | end 66 | end 67 | 68 | def doc_coverage 69 | return 0 if acl_included == 0 70 | 71 | (100 * documented) / acl_included 72 | end 73 | 74 | private 75 | 76 | def comma_list(items) 77 | case items.count 78 | when 0 then '' 79 | when 1 then items[0] 80 | when 2 then "#{items[0]} or #{items[1]}" 81 | else "#{items[0..-2].join(', ')}, or #{items[-1]}" 82 | end 83 | end 84 | 85 | def symbol_or_symbols(count) 86 | count == 1 ? 'symbol' : 'symbols' 87 | end 88 | end 89 | end 90 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | ## Code of Conduct 2 | 3 | This project adheres to the [Contributor Covenant Code of Conduct](https://realm.io/conduct). 4 | By participating, you are expected to uphold this code. Please report 5 | unacceptable behavior to [info@realm.io](mailto:info@realm.io). 6 | 7 | ## Tracking changes 8 | 9 | All changes should be made via pull requests on GitHub. 10 | 11 | When issuing a pull request, please add a summary of your changes to 12 | the `CHANGELOG.md` file. 13 | 14 | We follow the same syntax as CocoaPods' CHANGELOG.md: 15 | 16 | 1. One Markdown unnumbered list item describing the change. 17 | 2. Two trailing spaces on the last line describing the change. 18 | 3. A list of Markdown hyperlinks to the contributors to the change. One entry 19 | per line (and there's usually just one contributor). 20 | 4. A list of Markdown hyperlinks to the issues the change addresses. One entry 21 | per line (and there's usually just one hyperlink). Don't link to PRs here. 22 | 5. All CHANGELOG.md content is hard-wrapped at 80 characters. 23 | 24 | ## Updating the integration specs 25 | 26 | Jazzy heavily relies on integration tests, but since they're considerably large 27 | and noisy, we keep them in a separate repo 28 | ([realm/jazzy-integration-specs](https://github.com/realm/jazzy-integration-specs)). 29 | 30 | If you're making a PR towards jazzy that affects the generated docs, please 31 | update the integration specs using the following process: 32 | 33 | ```shell 34 | git checkout master 35 | git pull 36 | git checkout - 37 | git rebase master 38 | rake bootstrap 39 | bundle exec rake rebuild_integration_fixtures 40 | cd spec/integration_specs 41 | git checkout -b $jazzy_branch_name 42 | git commit -a -m "update for $jazzy_branch_name" 43 | git push 44 | cd ../../ 45 | git commit -a -m "update integration specs" 46 | git push 47 | ``` 48 | 49 | You'll need push access to the integration specs repo to do this. You can 50 | request access from one of the maintainers when filing your PR. 51 | 52 | You must have Xcode 26.1 beta 1 installed to build the integration specs. 53 | 54 | ## Making changes to SourceKitten 55 | 56 | When changes are landed in the https://github.com/jpsim/SourceKitten repo the 57 | SourceKitten framework located in jazzy must be updated. 58 | 59 | The following may be executed from your `jazzy/` directory. 60 | 61 | ``` 62 | cd SourceKitten 63 | git checkout master 64 | git pull 65 | cd .. 66 | rake sourcekitten 67 | git add . 68 | git commit -m "..." 69 | ``` 70 | -------------------------------------------------------------------------------- /lib/jazzy/source_document.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'pathname' 4 | 5 | require 'jazzy/jazzy_markdown' 6 | 7 | module Jazzy 8 | # Standalone markdown docs including index.html 9 | class SourceDocument < SourceDeclaration 10 | attr_accessor :overview 11 | attr_accessor :readme_path 12 | 13 | def initialize 14 | super 15 | self.children = [] 16 | self.parameters = [] 17 | self.abstract = '' 18 | self.type = SourceDeclaration::Type.markdown 19 | self.mark = SourceMark.new 20 | end 21 | 22 | def self.make_index(readme_path) 23 | SourceDocument.new.tap do |sd| 24 | sd.name = 'index' 25 | sd.url = sd.name + '.html' 26 | sd.readme_path = readme_path 27 | end 28 | end 29 | 30 | def readme? 31 | url == 'index.html' 32 | end 33 | 34 | def render_as_page? 35 | true 36 | end 37 | 38 | def omit_content_from_parent? 39 | true 40 | end 41 | 42 | def config 43 | Config.instance 44 | end 45 | 46 | def url_name 47 | name.downcase.strip.tr(' ', '-').gsub(/[^[[:word:]]-]/, '') 48 | end 49 | 50 | def content(source_module) 51 | return readme_content(source_module) if name == 'index' 52 | 53 | overview 54 | end 55 | 56 | def readme_content(source_module) 57 | config_readme || fallback_readme || generated_readme(source_module) 58 | end 59 | 60 | def config_readme 61 | readme_path.read if readme_path&.exist? 62 | end 63 | 64 | def fallback_readme 65 | %w[README.md README.markdown README.mdown README].each do |potential_name| 66 | file = config.source_directory + potential_name 67 | return file.read if file.exist? 68 | end 69 | false 70 | end 71 | 72 | def generated_readme(source_module) 73 | if podspec = config.podspec 74 | ### License 75 | 76 | # #{license[:license]} 77 | <<-README 78 | # #{podspec.name} 79 | 80 | ### #{podspec.summary} 81 | 82 | #{podspec.description} 83 | 84 | ### Installation 85 | 86 | ```ruby 87 | pod '#{podspec.name}' 88 | ``` 89 | 90 | ### Authors 91 | 92 | #{source_module.author_name} 93 | README 94 | else 95 | <<-README 96 | # #{source_module.readme_title} 97 | 98 | ### Authors 99 | 100 | #{source_module.author_name} 101 | README 102 | end 103 | end 104 | end 105 | end 106 | -------------------------------------------------------------------------------- /lib/jazzy/source_host.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Jazzy 4 | # Deal with different source code repositories 5 | module SourceHost 6 | # Factory to create the right source host 7 | def self.create(options) 8 | return unless options.source_host_url || options.source_host_files_url 9 | 10 | case options.source_host 11 | when :github then GitHub.new 12 | when :gitlab then GitLab.new 13 | when :bitbucket then BitBucket.new 14 | end 15 | end 16 | 17 | # Use GitHub as the default behaviour. 18 | class GitHub 19 | include Config::Mixin 20 | 21 | # Human readable name, appears in UI 22 | def name 23 | 'GitHub' 24 | end 25 | 26 | # Jazzy extension with logo 27 | def extension 28 | name.downcase 29 | end 30 | 31 | # Logo image filename within extension 32 | def image 33 | 'gh.png' 34 | end 35 | 36 | # URL to link to from logo 37 | def url 38 | config.source_host_url 39 | end 40 | 41 | # URL to link to from a SourceDeclaration. 42 | # Compare using `realpath` because `item.file` comes out of 43 | # SourceKit/etc. 44 | def item_url(item) 45 | return unless files_url && item.file 46 | 47 | realpath = item.file.realpath 48 | return unless realpath.to_path.start_with?(local_root_realpath) 49 | 50 | path = realpath.relative_path_from(local_root_realpath) 51 | fragment = 52 | if item.start_line && (item.start_line != item.end_line) 53 | item_url_multiline_fragment(item.start_line, item.end_line) 54 | else 55 | item_url_line_fragment(item.line) 56 | end 57 | 58 | "#{files_url}/#{path}##{fragment}" 59 | end 60 | 61 | private 62 | 63 | def files_url 64 | config.source_host_files_url 65 | end 66 | 67 | def local_root_realpath 68 | @local_root_realpath ||= config.source_directory.realpath.to_path 69 | end 70 | 71 | # Source host's line numbering link scheme 72 | def item_url_line_fragment(line) 73 | "L#{line}" 74 | end 75 | 76 | def item_url_multiline_fragment(start_line, end_line) 77 | "L#{start_line}-L#{end_line}" 78 | end 79 | end 80 | 81 | # GitLab very similar to GitHub 82 | class GitLab < GitHub 83 | def name 84 | 'GitLab' 85 | end 86 | 87 | def image 88 | 'gitlab.svg' 89 | end 90 | end 91 | 92 | # BitBucket has its own line number system 93 | class BitBucket < GitHub 94 | def name 95 | 'Bitbucket' 96 | end 97 | 98 | def image 99 | 'bitbucket.svg' 100 | end 101 | 102 | def item_url_line_fragment(line) 103 | "lines-#{line}" 104 | end 105 | 106 | def item_url_multiline_fragment(start_line, end_line) 107 | "lines-#{start_line}:#{end_line}" 108 | end 109 | end 110 | end 111 | end 112 | -------------------------------------------------------------------------------- /lib/jazzy/symbol_graph/constraint.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Jazzy 4 | module SymbolGraph 5 | # Constraint is a tidied-up JSON object, used by both Symbol and 6 | # Relationship, and key to reconstructing extensions. 7 | class Constraint 8 | attr_accessor :kind 9 | attr_accessor :lhs 10 | attr_accessor :rhs 11 | 12 | private 13 | 14 | def initialize(kind, lhs, rhs) 15 | self.kind = kind # "==" or ":" 16 | self.lhs = lhs 17 | self.rhs = rhs 18 | end 19 | 20 | KIND_MAP = { 21 | 'conformance' => ':', 22 | 'superclass' => ':', 23 | 'sameType' => '==', 24 | }.freeze 25 | private_constant :KIND_MAP 26 | 27 | public 28 | 29 | # Init from a JSON hash 30 | def self.new_hash(hash) 31 | kind = KIND_MAP[hash[:kind]] 32 | raise "Unknown constraint kind '#{kind}'" unless kind 33 | 34 | lhs = hash[:lhs].sub(/^Self\./, '') 35 | rhs = hash[:rhs].sub(/^Self\./, '') 36 | new(kind, lhs, rhs) 37 | end 38 | 39 | # Init from a Swift declaration fragment eg. 'A : B' 40 | def self.new_declaration(decl) 41 | decl =~ /^(.*?)\s*([:<=]+)\s*(.*)$/ 42 | new(Regexp.last_match[2], 43 | Regexp.last_match[1], 44 | Regexp.last_match[3]) 45 | end 46 | 47 | def to_swift 48 | "#{lhs} #{kind} #{rhs}" 49 | end 50 | 51 | # The first component of types in the constraint 52 | def type_names 53 | Set.new([lhs, rhs].map { |n| n.sub(/\..*$/, '') }) 54 | end 55 | 56 | def self.new_list(hash_list) 57 | hash_list.map { |h| Constraint.new_hash(h) }.sort.uniq 58 | end 59 | 60 | # Swift protocols and reqs have an implementation/hidden conformance 61 | # to their own protocol: we don't want to think about this in docs. 62 | def self.new_list_for_symbol(hash_list, path_components) 63 | hash_list.map do |hash| 64 | if hash[:lhs] == 'Self' && 65 | hash[:kind] == 'conformance' && 66 | path_components.include?(hash[:rhs]) 67 | next nil 68 | end 69 | 70 | Constraint.new_hash(hash) 71 | end.compact 72 | end 73 | 74 | # Workaround Swift 5.3 bug with missing constraint rels, eg. 75 | # extension P { 76 | # func f(a: C) where C: P {} 77 | # } 78 | def self.new_list_from_declaration(decl) 79 | return [] if decl.include?('(') 80 | 81 | decl.split(/\s*,\s*/).map { |cons| Constraint.new_declaration(cons) } 82 | end 83 | 84 | # Sort order - by Swift text 85 | include Comparable 86 | 87 | def <=>(other) 88 | to_swift <=> other.to_swift 89 | end 90 | 91 | alias eql? == 92 | 93 | def hash 94 | to_swift.hash 95 | end 96 | end 97 | end 98 | end 99 | 100 | class Array 101 | def to_where_clause 102 | empty? ? '' : " where #{map(&:to_swift).join(', ')}" 103 | end 104 | end 105 | -------------------------------------------------------------------------------- /lib/jazzy/source_declaration/access_control_level.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Jazzy 4 | class SourceDeclaration 5 | class AccessControlLevel 6 | include Comparable 7 | 8 | # Order matters 9 | LEVELS = %i[private fileprivate internal package public open].freeze 10 | 11 | LEVELS_INDEX = LEVELS.to_h { |i| [i, LEVELS.index(i)] }.freeze 12 | 13 | attr_reader :level 14 | 15 | def initialize(level) 16 | @level = level 17 | end 18 | 19 | # From a SourceKit accessibility string 20 | def self.from_accessibility(accessibility) 21 | return nil if accessibility.nil? 22 | 23 | if accessibility =~ /^source\.lang\.swift\.accessibility\.(.*)$/ && 24 | (matched = Regexp.last_match(1).to_sym) && 25 | !LEVELS_INDEX[matched].nil? 26 | return new(matched) 27 | end 28 | 29 | raise "cannot initialize AccessControlLevel with '#{accessibility}'" 30 | end 31 | 32 | # From a SourceKit declaration hash 33 | def self.from_doc(doc) 34 | return AccessControlLevel.internal if implicit_deinit?(doc) 35 | 36 | from_documentation_attribute(doc) || 37 | from_accessibility(doc['key.accessibility']) || 38 | from_doc_explicit_declaration(doc) || 39 | AccessControlLevel.internal # fallback on internal ACL 40 | end 41 | 42 | # Workaround `deinit` being always technically public 43 | def self.implicit_deinit?(doc) 44 | doc['key.name'] == 'deinit' && 45 | from_doc_explicit_declaration(doc).nil? 46 | end 47 | 48 | # From a Swift declaration 49 | def self.from_doc_explicit_declaration(doc) 50 | declaration = doc['key.parsed_declaration'] 51 | LEVELS.each do |level| 52 | if declaration =~ /\b#{level}\b/ 53 | return send(level) 54 | end 55 | end 56 | nil 57 | end 58 | 59 | # From a config instruction 60 | def self.from_human_string(string) 61 | normalized = string.to_s.downcase.to_sym 62 | if LEVELS_INDEX[normalized].nil? 63 | raise "cannot initialize AccessControlLevel with '#{string}'" 64 | end 65 | 66 | send(normalized) 67 | end 68 | 69 | # From a @_documentation(visibility:) attribute 70 | def self.from_documentation_attribute(doc) 71 | if doc['key.annotated_decl'] =~ /@_documentation\(\s*visibility\s*:\s*(\w+)/ 72 | from_human_string(Regexp.last_match[1]) 73 | end 74 | end 75 | 76 | # Define `AccessControlLevel.public` etc. 77 | 78 | LEVELS.each do |level| 79 | define_singleton_method(level) do 80 | new(level) 81 | end 82 | end 83 | 84 | # Comparing access levels 85 | 86 | def <=>(other) 87 | LEVELS_INDEX[level] <=> LEVELS_INDEX[other.level] 88 | end 89 | 90 | def included_levels 91 | LEVELS_INDEX.select { |_, v| v >= LEVELS_INDEX[level] }.keys 92 | end 93 | 94 | def excluded_levels 95 | LEVELS_INDEX.select { |_, v| v < LEVELS_INDEX[level] }.keys 96 | end 97 | end 98 | end 99 | end 100 | -------------------------------------------------------------------------------- /lib/jazzy/themes/apple/assets/css/highlight.css.scss: -------------------------------------------------------------------------------- 1 | /*! Jazzy - https://github.com/realm/jazzy 2 | * Copyright Realm Inc. 3 | * SPDX-License-Identifier: MIT 4 | */ 5 | /* Credit to https://gist.github.com/wataru420/2048287 */ 6 | 7 | .highlight { 8 | .c { color: #999988; font-style: italic } // Comment 9 | .err { color: #a61717; background-color: #e3d2d2 } // Error 10 | .k { color: #000000; font-weight: bold } // Keyword 11 | .o { color: #000000; font-weight: bold } // Operator 12 | .cm { color: #999988; font-style: italic } // Comment.Multiline 13 | .cp { color: #999999; font-weight: bold } // Comment.Preproc 14 | .c1 { color: #999988; font-style: italic } // Comment.Single 15 | .cs { color: #999999; font-weight: bold; font-style: italic } // Comment.Special 16 | .gd { color: #000000; background-color: #ffdddd } // Generic.Deleted 17 | .gd .x { color: #000000; background-color: #ffaaaa } // Generic.Deleted.Specific 18 | .ge { color: #000000; font-style: italic } // Generic.Emph 19 | .gr { color: #aa0000 } // Generic.Error 20 | .gh { color: #999999 } // Generic.Heading 21 | .gi { color: #000000; background-color: #ddffdd } // Generic.Inserted 22 | .gi .x { color: #000000; background-color: #aaffaa } // Generic.Inserted.Specific 23 | .go { color: #888888 } // Generic.Output 24 | .gp { color: #555555 } // Generic.Prompt 25 | .gs { font-weight: bold } // Generic.Strong 26 | .gu { color: #aaaaaa } // Generic.Subheading 27 | .gt { color: #aa0000 } // Generic.Traceback 28 | .kc { color: #000000; font-weight: bold } // Keyword.Constant 29 | .kd { color: #000000; font-weight: bold } // Keyword.Declaration 30 | .kp { color: #000000; font-weight: bold } // Keyword.Pseudo 31 | .kr { color: #000000; font-weight: bold } // Keyword.Reserved 32 | .kt { color: #445588; } // Keyword.Type 33 | .m { color: #009999 } // Literal.Number 34 | .s { color: #d14 } // Literal.String 35 | .na { color: #008080 } // Name.Attribute 36 | .nb { color: #0086B3 } // Name.Builtin 37 | .nc { color: #445588; font-weight: bold } // Name.Class 38 | .no { color: #008080 } // Name.Constant 39 | .ni { color: #800080 } // Name.Entity 40 | .ne { color: #990000; font-weight: bold } // Name.Exception 41 | .nf { color: #990000; } // Name.Function 42 | .nn { color: #555555 } // Name.Namespace 43 | .nt { color: #000080 } // Name.Tag 44 | .nv { color: #008080 } // Name.Variable 45 | .ow { color: #000000; font-weight: bold } // Operator.Word 46 | .w { color: #bbbbbb } // Text.Whitespace 47 | .mf { color: #009999 } // Literal.Number.Float 48 | .mh { color: #009999 } // Literal.Number.Hex 49 | .mi { color: #009999 } // Literal.Number.Integer 50 | .mo { color: #009999 } // Literal.Number.Oct 51 | .sb { color: #d14 } // Literal.String.Backtick 52 | .sc { color: #d14 } // Literal.String.Char 53 | .sd { color: #d14 } // Literal.String.Doc 54 | .s2 { color: #d14 } // Literal.String.Double 55 | .se { color: #d14 } // Literal.String.Escape 56 | .sh { color: #d14 } // Literal.String.Heredoc 57 | .si { color: #d14 } // Literal.String.Interpol 58 | .sx { color: #d14 } // Literal.String.Other 59 | .sr { color: #009926 } // Literal.String.Regex 60 | .s1 { color: #d14 } // Literal.String.Single 61 | .ss { color: #990073 } // Literal.String.Symbol 62 | .bp { color: #999999 } // Name.Builtin.Pseudo 63 | .vc { color: #008080 } // Name.Variable.Class 64 | .vg { color: #008080 } // Name.Variable.Global 65 | .vi { color: #008080 } // Name.Variable.Instance 66 | .il { color: #009999 } // Literal.Number.Integer.Long 67 | } 68 | -------------------------------------------------------------------------------- /lib/jazzy/themes/jony/assets/css/highlight.css.scss: -------------------------------------------------------------------------------- 1 | /*! Jazzy - https://github.com/realm/jazzy 2 | * Copyright Realm Inc. 3 | * SPDX-License-Identifier: MIT 4 | */ 5 | /* Credit to https://gist.github.com/wataru420/2048287 */ 6 | 7 | .highlight { 8 | .c { color: #999988; font-style: italic } // Comment 9 | .err { color: #a61717; background-color: #e3d2d2 } // Error 10 | .k { color: #000000; font-weight: bold } // Keyword 11 | .o { color: #000000; font-weight: bold } // Operator 12 | .cm { color: #999988; font-style: italic } // Comment.Multiline 13 | .cp { color: #999999; font-weight: bold } // Comment.Preproc 14 | .c1 { color: #999988; font-style: italic } // Comment.Single 15 | .cs { color: #999999; font-weight: bold; font-style: italic } // Comment.Special 16 | .gd { color: #000000; background-color: #ffdddd } // Generic.Deleted 17 | .gd .x { color: #000000; background-color: #ffaaaa } // Generic.Deleted.Specific 18 | .ge { color: #000000; font-style: italic } // Generic.Emph 19 | .gr { color: #aa0000 } // Generic.Error 20 | .gh { color: #999999 } // Generic.Heading 21 | .gi { color: #000000; background-color: #ddffdd } // Generic.Inserted 22 | .gi .x { color: #000000; background-color: #aaffaa } // Generic.Inserted.Specific 23 | .go { color: #888888 } // Generic.Output 24 | .gp { color: #555555 } // Generic.Prompt 25 | .gs { font-weight: bold } // Generic.Strong 26 | .gu { color: #aaaaaa } // Generic.Subheading 27 | .gt { color: #aa0000 } // Generic.Traceback 28 | .kc { color: #000000; font-weight: bold } // Keyword.Constant 29 | .kd { color: #000000; font-weight: bold } // Keyword.Declaration 30 | .kp { color: #000000; font-weight: bold } // Keyword.Pseudo 31 | .kr { color: #000000; font-weight: bold } // Keyword.Reserved 32 | .kt { color: #445588; } // Keyword.Type 33 | .m { color: #009999 } // Literal.Number 34 | .s { color: #d14 } // Literal.String 35 | .na { color: #008080 } // Name.Attribute 36 | .nb { color: #0086B3 } // Name.Builtin 37 | .nc { color: #445588; font-weight: bold } // Name.Class 38 | .no { color: #008080 } // Name.Constant 39 | .ni { color: #800080 } // Name.Entity 40 | .ne { color: #990000; font-weight: bold } // Name.Exception 41 | .nf { color: #990000; } // Name.Function 42 | .nn { color: #555555 } // Name.Namespace 43 | .nt { color: #000080 } // Name.Tag 44 | .nv { color: #008080 } // Name.Variable 45 | .ow { color: #000000; font-weight: bold } // Operator.Word 46 | .w { color: #bbbbbb } // Text.Whitespace 47 | .mf { color: #009999 } // Literal.Number.Float 48 | .mh { color: #009999 } // Literal.Number.Hex 49 | .mi { color: #009999 } // Literal.Number.Integer 50 | .mo { color: #009999 } // Literal.Number.Oct 51 | .sb { color: #d14 } // Literal.String.Backtick 52 | .sc { color: #d14 } // Literal.String.Char 53 | .sd { color: #d14 } // Literal.String.Doc 54 | .s2 { color: #d14 } // Literal.String.Double 55 | .se { color: #d14 } // Literal.String.Escape 56 | .sh { color: #d14 } // Literal.String.Heredoc 57 | .si { color: #d14 } // Literal.String.Interpol 58 | .sx { color: #d14 } // Literal.String.Other 59 | .sr { color: #009926 } // Literal.String.Regex 60 | .s1 { color: #d14 } // Literal.String.Single 61 | .ss { color: #990073 } // Literal.String.Symbol 62 | .bp { color: #999999 } // Name.Builtin.Pseudo 63 | .vc { color: #008080 } // Name.Variable.Class 64 | .vg { color: #008080 } // Name.Variable.Global 65 | .vi { color: #008080 } // Name.Variable.Instance 66 | .il { color: #009999 } // Literal.Number.Integer.Long 67 | } 68 | -------------------------------------------------------------------------------- /lib/jazzy/themes/fullwidth/assets/css/highlight.css.scss: -------------------------------------------------------------------------------- 1 | /*! Jazzy - https://github.com/realm/jazzy 2 | * Copyright Realm Inc. 3 | * SPDX-License-Identifier: MIT 4 | */ 5 | /* Credit to https://gist.github.com/wataru420/2048287 */ 6 | 7 | .highlight { 8 | .c { color: #999988; font-style: italic } // Comment 9 | .err { color: #a61717; background-color: #e3d2d2 } // Error 10 | .k { color: #000000; font-weight: bold } // Keyword 11 | .o { color: #000000; font-weight: bold } // Operator 12 | .cm { color: #999988; font-style: italic } // Comment.Multiline 13 | .cp { color: #999999; font-weight: bold } // Comment.Preproc 14 | .c1 { color: #999988; font-style: italic } // Comment.Single 15 | .cs { color: #999999; font-weight: bold; font-style: italic } // Comment.Special 16 | .gd { color: #000000; background-color: #ffdddd } // Generic.Deleted 17 | .gd .x { color: #000000; background-color: #ffaaaa } // Generic.Deleted.Specific 18 | .ge { color: #000000; font-style: italic } // Generic.Emph 19 | .gr { color: #aa0000 } // Generic.Error 20 | .gh { color: #999999 } // Generic.Heading 21 | .gi { color: #000000; background-color: #ddffdd } // Generic.Inserted 22 | .gi .x { color: #000000; background-color: #aaffaa } // Generic.Inserted.Specific 23 | .go { color: #888888 } // Generic.Output 24 | .gp { color: #555555 } // Generic.Prompt 25 | .gs { font-weight: bold } // Generic.Strong 26 | .gu { color: #aaaaaa } // Generic.Subheading 27 | .gt { color: #aa0000 } // Generic.Traceback 28 | .kc { color: #000000; font-weight: bold } // Keyword.Constant 29 | .kd { color: #000000; font-weight: bold } // Keyword.Declaration 30 | .kp { color: #000000; font-weight: bold } // Keyword.Pseudo 31 | .kr { color: #000000; font-weight: bold } // Keyword.Reserved 32 | .kt { color: #445588; } // Keyword.Type 33 | .m { color: #009999 } // Literal.Number 34 | .s { color: #d14 } // Literal.String 35 | .na { color: #008080 } // Name.Attribute 36 | .nb { color: #0086B3 } // Name.Builtin 37 | .nc { color: #445588; font-weight: bold } // Name.Class 38 | .no { color: #008080 } // Name.Constant 39 | .ni { color: #800080 } // Name.Entity 40 | .ne { color: #990000; font-weight: bold } // Name.Exception 41 | .nf { color: #990000; } // Name.Function 42 | .nn { color: #555555 } // Name.Namespace 43 | .nt { color: #000080 } // Name.Tag 44 | .nv { color: #008080 } // Name.Variable 45 | .ow { color: #000000; font-weight: bold } // Operator.Word 46 | .w { color: #bbbbbb } // Text.Whitespace 47 | .mf { color: #009999 } // Literal.Number.Float 48 | .mh { color: #009999 } // Literal.Number.Hex 49 | .mi { color: #009999 } // Literal.Number.Integer 50 | .mo { color: #009999 } // Literal.Number.Oct 51 | .sb { color: #d14 } // Literal.String.Backtick 52 | .sc { color: #d14 } // Literal.String.Char 53 | .sd { color: #d14 } // Literal.String.Doc 54 | .s2 { color: #d14 } // Literal.String.Double 55 | .se { color: #d14 } // Literal.String.Escape 56 | .sh { color: #d14 } // Literal.String.Heredoc 57 | .si { color: #d14 } // Literal.String.Interpol 58 | .sx { color: #d14 } // Literal.String.Other 59 | .sr { color: #009926 } // Literal.String.Regex 60 | .s1 { color: #d14 } // Literal.String.Single 61 | .ss { color: #990073 } // Literal.String.Symbol 62 | .bp { color: #999999 } // Name.Builtin.Pseudo 63 | .vc { color: #008080 } // Name.Variable.Class 64 | .vg { color: #008080 } // Name.Variable.Global 65 | .vi { color: #008080 } // Name.Variable.Instance 66 | .il { color: #009999 } // Literal.Number.Integer.Long 67 | } 68 | -------------------------------------------------------------------------------- /lib/jazzy/themes/apple/templates/task.mustache: -------------------------------------------------------------------------------- 1 |
2 | {{#name}} 3 |
4 | 5 | 6 |
7 | 8 |

{{{name_html}}}

9 |
10 |
11 | {{/name}} 12 | 100 |
101 | -------------------------------------------------------------------------------- /lib/jazzy/themes/jony/templates/task.mustache: -------------------------------------------------------------------------------- 1 |
2 | {{#name}} 3 |
4 | 5 | 6 |
7 | 8 |

{{{name_html}}}

9 |
10 |
11 | {{/name}} 12 | 100 |
101 | -------------------------------------------------------------------------------- /lib/jazzy/themes/apple/templates/doc.mustache: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | {{name}} {{kind}} Reference 5 | 6 | 7 | {{#enable_katex}} 8 | 9 | {{/enable_katex}} 10 | 11 | 12 | {{#enable_katex}} 13 | 14 | {{/enable_katex}} 15 | 16 | {{{custom_head}}} 17 | {{^disable_search}} 18 | 19 | 20 | 21 | {{/disable_search}} 22 | 23 | 24 | {{#dash_type}} 25 | 26 | {{/dash_type}} 27 | 28 | {{> header}} 29 |
30 | 42 |
43 |
44 | {{> nav}} 45 |
46 |
47 |
48 | {{^hide_name}}

{{name}}

{{/hide_name}} 49 | {{> deprecation}} 50 | {{#declaration}} 51 |
52 |
53 | {{#other_language_declaration}}

{{language}}

{{/other_language_declaration}} 54 | {{{declaration}}} 55 |
56 | {{#other_language_declaration}} 57 |
58 |

Swift

59 | {{{other_language_declaration}}} 60 |
61 | {{/other_language_declaration}} 62 |
63 | {{/declaration}} 64 | {{{overview}}} 65 | {{#parameters.any?}} 66 |
67 |

Parameters

68 | 69 | 70 | {{#parameters}} 71 | {{> parameter}} 72 | {{/parameters}} 73 | 74 |
75 |
76 | {{/parameters.any?}} 77 | {{#return}} 78 |
79 |

Return Value

80 | {{{return}}} 81 |
82 | {{/return}} 83 | {{#source_host_item_url}} 84 | 87 | {{/source_host_item_url}} 88 |
89 | {{> tasks}} 90 |
91 | {{> footer}} 92 |
93 |
94 | 95 | 96 | -------------------------------------------------------------------------------- /lib/jazzy/themes/fullwidth/templates/task.mustache: -------------------------------------------------------------------------------- 1 |
2 | {{#name}} 3 |
4 | 5 | 6 |
7 | 8 |

{{{name_html}}}

9 |
10 |
11 | {{/name}} 12 | 100 |
101 | -------------------------------------------------------------------------------- /lib/jazzy/themes/fullwidth/templates/doc.mustache: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | {{name}} {{kind}} Reference 5 | 6 | 7 | {{#enable_katex}} 8 | 9 | {{/enable_katex}} 10 | 11 | 12 | {{#enable_katex}} 13 | 14 | {{/enable_katex}} 15 | 16 | {{{custom_head}}} 17 | {{^disable_search}} 18 | 19 | 20 | 21 | {{/disable_search}} 22 | 23 | 24 | 25 | {{#dash_type}} 26 | 27 | {{/dash_type}} 28 | 29 | 30 | 31 | {{> header}} 32 | 33 | 45 | 46 |
47 | {{> nav}} 48 |
49 | 50 |
51 |
52 | {{^hide_name}}

{{name}}

{{/hide_name}} 53 | {{> deprecation}} 54 | {{#declaration}} 55 |
56 |
57 | {{#other_language_declaration}}

{{language}}

{{/other_language_declaration}} 58 | {{{declaration}}} 59 |
60 | {{#other_language_declaration}} 61 |
62 |

Swift

63 | {{{other_language_declaration}}} 64 |
65 | {{/other_language_declaration}} 66 |
67 | {{/declaration}} 68 | {{{overview}}} 69 | {{#parameters.any?}} 70 |
71 |

Parameters

72 | 73 | 74 | {{#parameters}} 75 | {{> parameter}} 76 | {{/parameters}} 77 | 78 |
79 |
80 | {{/parameters.any?}} 81 | {{#return}} 82 |
83 |

Return Value

84 | {{{return}}} 85 |
86 | {{/return}} 87 | {{#source_host_item_url}} 88 | 91 | {{/source_host_item_url}} 92 |
93 |
94 | 95 | {{> tasks}} 96 | 97 |
98 |
99 | {{> footer}} 100 | 101 | 102 | -------------------------------------------------------------------------------- /lib/jazzy/themes/jony/templates/doc.mustache: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | {{name}} {{kind}} Reference 5 | 6 | 7 | {{#enable_katex}} 8 | 9 | {{/enable_katex}} 10 | 11 | 12 | 13 | {{#enable_katex}} 14 | 15 | {{/enable_katex}} 16 | 17 | {{{custom_head}}} 18 | 19 | 20 | {{#dash_type}} 21 | 22 | {{/dash_type}} 23 | 24 | {{> header}} 25 | 43 |
44 |
45 |
46 |
47 |
48 | {{^hide_name}}

{{name}}

{{/hide_name}} 49 | {{> deprecation}} 50 | {{#declaration}} 51 |
52 |
53 | {{#other_language_declaration}}

{{language}}

{{/other_language_declaration}} 54 | {{{declaration}}} 55 |
56 | {{#other_language_declaration}} 57 |
58 |

Swift

59 | {{{other_language_declaration}}} 60 |
61 | {{/other_language_declaration}} 62 |
63 | {{/declaration}} 64 | {{{overview}}} 65 | {{#parameters.any?}} 66 |
67 |

Parameters

68 | 69 | 70 | {{#parameters}} 71 | {{> parameter}} 72 | {{/parameters}} 73 | 74 |
75 |
76 | {{/parameters.any?}} 77 | {{#return}} 78 |
79 |

Return Value

80 | {{{return}}} 81 |
82 | {{/return}} 83 | {{#source_host_item_url}} 84 | 87 | {{/source_host_item_url}} 88 |
89 | {{> tasks}} 90 |
91 |
92 |
93 | 96 | 99 |
100 | 101 | 102 | -------------------------------------------------------------------------------- /lib/jazzy/symbol_graph.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'set' 4 | require 'jazzy/symbol_graph/graph' 5 | require 'jazzy/symbol_graph/constraint' 6 | require 'jazzy/symbol_graph/symbol' 7 | require 'jazzy/symbol_graph/relationship' 8 | require 'jazzy/symbol_graph/sym_node' 9 | require 'jazzy/symbol_graph/ext_node' 10 | require 'jazzy/symbol_graph/ext_key' 11 | 12 | # This is the top-level symbolgraph driver that deals with 13 | # figuring out arguments, running the tool, and loading the 14 | # results. 15 | 16 | module Jazzy 17 | module SymbolGraph 18 | # Find swift symbol graph files, either having been passed 19 | # in directly, or generated by running`swift symbolgraph-extract` 20 | # with configured args. 21 | # Then parse the results, and return as JSON in SourceKit[ten] 22 | # format. 23 | def self.build(module_config) 24 | if module_config.symbolgraph_directory.nil? 25 | Dir.mktmpdir do |tmp_dir| 26 | args = arguments(module_config, tmp_dir) 27 | 28 | Executable.execute_command('swift', 29 | args.unshift('symbolgraph-extract'), 30 | true) # raise on error 31 | 32 | parse_symbols(tmp_dir) 33 | end 34 | else 35 | parse_symbols(module_config.symbolgraph_directory.to_s) 36 | end 37 | end 38 | 39 | # Figure out the args to pass to symbolgraph-extract 40 | def self.arguments(module_config, output_path) 41 | if module_config.module_name.empty? 42 | raise 'error: `--swift-build-tool symbolgraph` requires `--module`.' 43 | end 44 | 45 | user_args = module_config.build_tool_arguments.join 46 | 47 | if user_args =~ /-(?:module-name|minimum-access-level|output-dir)/ 48 | raise 'error: `--build-tool-arguments` for ' \ 49 | "`--swift-build-tool symbolgraph` can't use `-module`, " \ 50 | '`-minimum-access-level`, or `-output-dir`.' 51 | end 52 | 53 | # Default set 54 | args = [ 55 | '-module-name', module_config.module_name, 56 | '-minimum-access-level', 'private', 57 | '-output-dir', output_path, 58 | '-skip-synthesized-members' 59 | ] 60 | 61 | # Things user can override 62 | args += ['-sdk', sdk(module_config)] unless user_args =~ /-sdk/ 63 | args += ['-target', target] unless user_args =~ /-target/ 64 | args += ['-F', module_config.source_directory.to_s] unless user_args =~ /-F(?!s)/ 65 | args += ['-I', module_config.source_directory.to_s] unless user_args =~ /-I/ 66 | 67 | args + module_config.build_tool_arguments 68 | end 69 | 70 | # Parse the symbol files in the given directory 71 | def self.parse_symbols(directory) 72 | Dir[directory + '/*.symbols.json'].sort.map do |filename| 73 | # The @ part is for extensions in our module (before the @) 74 | # of types in another module (after the @). 75 | File.basename(filename) =~ /(.*?)(@(.*?))?\.symbols/ 76 | module_name = Regexp.last_match[1] 77 | ext_module_name = Regexp.last_match[3] || module_name 78 | json = File.read(filename) 79 | { 80 | filename => 81 | Graph.new(json, module_name, ext_module_name).to_sourcekit, 82 | } 83 | end.to_json 84 | end 85 | 86 | # Get the SDK path. On !darwin this just isn't needed. 87 | def self.sdk(module_config) 88 | `xcrun --show-sdk-path --sdk #{module_config.sdk}`.chomp 89 | end 90 | 91 | # Guess a default LLVM target. Feels like the tool should figure this 92 | # out from sdk + the binary somehow? 93 | def self.target 94 | `swift -version` =~ /Target: (.*?)$/ 95 | Regexp.last_match[1] || 'x86_64-apple-macosx10.15' 96 | end 97 | 98 | # This is a last-ditch fallback for when symbolgraph doesn't 99 | # provide a name - at least conforming external types to local 100 | # protocols. 101 | def self.demangle(usr) 102 | args = %w[demangle -simplified -compact].append(usr.sub(/^s:/, 's')) 103 | output, = Executable.execute_command('swift', args, true) 104 | output.chomp 105 | rescue StandardError 106 | usr 107 | end 108 | end 109 | end 110 | -------------------------------------------------------------------------------- /lib/jazzy/extensions/gitlab/img/gitlab.svg: -------------------------------------------------------------------------------- 1 | 10 | 15 | 18 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /lib/jazzy/symbol_graph/ext_node.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'set' 4 | 5 | module Jazzy 6 | module SymbolGraph 7 | # For extensions we need to track constraints of the extended type 8 | # and the constraints introduced by the extension. 9 | class ExtConstraints 10 | attr_accessor :type # array 11 | attr_accessor :ext # array 12 | 13 | # all constraints inherited by members of the extension 14 | def merged 15 | (type + ext).sort 16 | end 17 | 18 | def initialize(type_constraints, ext_constraints) 19 | self.type = type_constraints || [] 20 | self.ext = ext_constraints || [] 21 | end 22 | end 23 | 24 | # An ExtNode is a node of the reconstructed syntax tree representing 25 | # an extension that we fabricate to resolve certain relationships. 26 | class ExtNode < BaseNode 27 | attr_accessor :usr 28 | attr_accessor :real_usr 29 | attr_accessor :name 30 | attr_accessor :all_constraints # ExtConstraints 31 | attr_accessor :conformances # set, can be empty 32 | 33 | # Deduce an extension from a member of an unknown type or 34 | # of known type with additional constraints 35 | def self.new_for_member(type_usr, 36 | member, 37 | constraints) 38 | new(type_usr, 39 | member.parent_qualified_name, 40 | constraints).tap { |o| o.add_child(member) } 41 | end 42 | 43 | # Deduce an extension from a protocol conformance for some type 44 | def self.new_for_conformance(type_usr, 45 | type_name, 46 | protocol, 47 | constraints) 48 | new(type_usr, type_name, constraints).tap do |o| 49 | o.add_conformance(protocol) 50 | end 51 | end 52 | 53 | private 54 | 55 | def initialize(usr, name, constraints) 56 | self.usr = usr 57 | self.name = name 58 | self.all_constraints = constraints 59 | self.conformances = Set.new 60 | super() 61 | end 62 | 63 | public 64 | 65 | def constraints 66 | all_constraints.merged 67 | end 68 | 69 | def add_conformance(protocol) 70 | conformances.add(protocol) 71 | end 72 | 73 | def full_declaration 74 | decl = "extension #{name}" 75 | unless conformances.empty? 76 | decl += " : #{conformances.sort.join(', ')}" 77 | end 78 | decl + all_constraints.ext.to_where_clause 79 | end 80 | 81 | def to_sourcekit(module_name, ext_module_name) 82 | declaration = full_declaration 83 | xml_declaration = "#{CGI.escapeHTML(declaration)}" 84 | 85 | hash = { 86 | 'key.kind' => 'source.lang.swift.decl.extension', 87 | 'key.usr' => real_usr || usr, 88 | 'key.name' => name, 89 | 'key.modulename' => ext_module_name, 90 | 'key.parsed_declaration' => declaration, 91 | 'key.annotated_decl' => xml_declaration, 92 | } 93 | 94 | unless conformances.empty? 95 | hash['key.inheritedtypes'] = conformances.sort.map do |conformance| 96 | { 'key.name' => conformance } 97 | end 98 | end 99 | 100 | add_children_to_sourcekit(hash, module_name) 101 | 102 | hash 103 | end 104 | 105 | # Sort order - by type name then constraint then conformances 106 | # Conformance check needed for stable order with Swift 5.9 107 | # extension symbols that can't merge as well as previously. 108 | include Comparable 109 | 110 | def sort_key 111 | name + constraints.map(&:to_swift).join + conformances.sort.join 112 | end 113 | 114 | def <=>(other) 115 | sort_key <=> other.sort_key 116 | end 117 | end 118 | 119 | # An ExtSymNode is an extension generated from a Swift 5.9 extension 120 | # symbol, for extensions of types from other modules only. 121 | class ExtSymNode < ExtNode 122 | attr_accessor :symbol 123 | 124 | def initialize(symbol) 125 | self.symbol = symbol 126 | super(symbol.usr, symbol.full_name, 127 | # sadly can't tell what constraints are inherited vs added 128 | ExtConstraints.new([], symbol.constraints)) 129 | end 130 | 131 | def to_sourcekit(module_name, ext_module_name) 132 | hash = super 133 | symbol.add_to_sourcekit(hash) 134 | end 135 | end 136 | end 137 | end 138 | -------------------------------------------------------------------------------- /lib/jazzy/docset_builder.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'mustache' 4 | require 'sqlite3' 5 | 6 | module Jazzy 7 | module DocBuilder 8 | # Follows the instructions found at https://kapeli.com/docsets#dashDocset. 9 | class DocsetBuilder 10 | include Config::Mixin 11 | 12 | attr_reader :output_dir 13 | attr_reader :generated_docs_dir 14 | attr_reader :source_module 15 | attr_reader :docset_dir 16 | attr_reader :documents_dir 17 | attr_reader :name 18 | 19 | def initialize(generated_docs_dir) 20 | @name = config.docset_title || config.module_names.first 21 | docset_path = config.docset_path || 22 | "docsets/#{safe_name}.docset" 23 | @docset_dir = generated_docs_dir + docset_path 24 | @generated_docs_dir = generated_docs_dir 25 | @output_dir = docset_dir.parent 26 | @documents_dir = docset_dir + 'Contents/Resources/Documents/' 27 | end 28 | 29 | def build!(all_declarations) 30 | docset_dir.rmtree if docset_dir.exist? 31 | copy_docs 32 | copy_icon if config.docset_icon 33 | write_plist 34 | create_index(all_declarations) 35 | create_archive 36 | create_xml if config.version && config.root_url 37 | end 38 | 39 | private 40 | 41 | def safe_name 42 | name.gsub(/[^a-z0-9_-]+/i, '_') 43 | end 44 | 45 | def write_plist 46 | info_plist_path = docset_dir + 'Contents/Info.plist' 47 | info_plist_path.open('w') do |plist| 48 | template = Pathname(__dir__) + 'docset_builder/info_plist.mustache' 49 | plist << Mustache.render( 50 | template.read, 51 | lowercase_name: name.downcase, 52 | lowercase_safe_name: safe_name.downcase, 53 | name: name, 54 | root_url: config.root_url, 55 | playground_url: config.docset_playground_url, 56 | ) 57 | end 58 | end 59 | 60 | def create_archive 61 | target = "#{safe_name}.tgz" 62 | source = docset_dir.basename.to_s 63 | options = { 64 | chdir: output_dir.to_s, 65 | [1, 2] => '/dev/null', # silence all output from `tar` 66 | } 67 | system('tar', "--exclude='.DS_Store'", '-cvzf', target, source, options) 68 | end 69 | 70 | def copy_docs 71 | files_to_copy = Pathname.glob(generated_docs_dir + '*') - 72 | [docset_dir, output_dir] 73 | 74 | FileUtils.mkdir_p documents_dir 75 | FileUtils.cp_r files_to_copy, documents_dir 76 | end 77 | 78 | def copy_icon 79 | FileUtils.cp config.docset_icon, docset_dir + 'icon.png' 80 | end 81 | 82 | def create_index(all_declarations) 83 | search_index_path = docset_dir + 'Contents/Resources/docSet.dsidx' 84 | SQLite3::Database.new(search_index_path.to_s) do |db| 85 | db.execute('CREATE TABLE searchIndex(' \ 86 | 'id INTEGER PRIMARY KEY, name TEXT, type TEXT, path TEXT);') 87 | db.execute('CREATE UNIQUE INDEX anchor ON ' \ 88 | 'searchIndex (name, type, path);') 89 | all_declarations.select(&:type).each do |doc| 90 | db.execute('INSERT OR IGNORE INTO searchIndex(name, type, path) ' \ 91 | 'VALUES (?, ?, ?);', [doc.name, doc.type.dash_type, doc.filepath]) 92 | end 93 | end 94 | end 95 | 96 | def create_xml 97 | (output_dir + "#{safe_name}.xml").open('w') do |xml| 98 | url = URI.join(config.root_url, "docsets/#{safe_name}.tgz") 99 | xml << "#{config.version}#{url}" \ 100 | "\n" 101 | end 102 | end 103 | 104 | # The web URL where the user intends to place the docset XML file. 105 | def dash_url 106 | return nil unless config.dash_url || config.root_url 107 | 108 | config.dash_url || 109 | URI.join( 110 | config.root_url, 111 | "docsets/#{safe_name}.xml", 112 | ) 113 | end 114 | 115 | public 116 | 117 | # The dash-feed:// URL that links from the Dash icon in generated 118 | # docs. This is passed to the Dash app and encodes the actual web 119 | # `dash_url` where the user has placed the XML file. 120 | # 121 | # Unfortunately for historical reasons this is *also* called the 122 | # 'dash_url' where it appears in mustache templates and so on. 123 | def dash_feed_url 124 | dash_url&.then do |url| 125 | "dash-feed://#{ERB::Util.url_encode(url.to_s)}" 126 | end 127 | end 128 | end 129 | end 130 | end 131 | -------------------------------------------------------------------------------- /lib/jazzy/symbol_graph/sym_node.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Jazzy 4 | module SymbolGraph 5 | # The rebuilt syntax tree is made of nodes that either match 6 | # symbols or that we fabricate for extensions. This is the common 7 | # treeishness. 8 | class BaseNode 9 | attr_accessor :children # array, can be empty 10 | attr_accessor :parent # can be nil 11 | 12 | def initialize 13 | self.children = [] 14 | end 15 | 16 | def add_child(child) 17 | child.parent = self 18 | children.append(child) 19 | end 20 | 21 | def add_children_to_sourcekit(hash, module_name) 22 | unless children.empty? 23 | hash['key.substructure'] = 24 | children.sort.map { |c| c.to_sourcekit(module_name) } 25 | end 26 | end 27 | end 28 | 29 | # A SymNode is a node of the reconstructed syntax tree holding a symbol. 30 | # It can turn itself into SourceKit and helps decode extensions. 31 | class SymNode < BaseNode 32 | attr_accessor :symbol 33 | attr_writer :override 34 | attr_writer :protocol_requirement 35 | attr_writer :unlisted 36 | attr_accessor :superclass_name 37 | 38 | def override? 39 | @override 40 | end 41 | 42 | def protocol_requirement? 43 | @protocol_requirement 44 | end 45 | 46 | def top_level_decl? 47 | !@unlisted && parent.nil? 48 | end 49 | 50 | def initialize(symbol) 51 | self.symbol = symbol 52 | super() 53 | end 54 | 55 | def qualified_name 56 | symbol.path_components.join('.') 57 | end 58 | 59 | def parent_qualified_name 60 | symbol.path_components[0...-1].join('.') 61 | end 62 | 63 | def protocol? 64 | symbol.kind.end_with?('protocol') 65 | end 66 | 67 | def actor? 68 | symbol.kind.end_with?('actor') 69 | end 70 | 71 | def constraints 72 | symbol.constraints 73 | end 74 | 75 | # Add another SymNode as a member if possible. 76 | # It must go in an extension if either: 77 | # - it has different generic constraints to us; or 78 | # - we're a protocol and it's a default impl / ext method 79 | def add_child?(node, unique_context_constraints) 80 | unless unique_context_constraints.empty? && 81 | (!protocol? || node.protocol_requirement?) 82 | return false 83 | end 84 | 85 | add_child(node) 86 | true 87 | end 88 | 89 | # The `Constraint`s on this decl that are both: 90 | # 1. Unique, ie. not just inherited from its context; and 91 | # 2. Constraining the *context's* gen params rather than our own. 92 | def unique_context_constraints(context) 93 | return symbol.constraints unless context 94 | 95 | new_generic_type_params = 96 | symbol.generic_type_params - context.symbol.generic_type_params 97 | 98 | (symbol.constraints - context.symbol.constraints) 99 | .select { |con| con.type_names.disjoint?(new_generic_type_params) } 100 | end 101 | 102 | # Messy check whether we need to fabricate an extension for a protocol 103 | # conformance: don't bother if it's already in the type declaration. 104 | def conformance?(protocol) 105 | return false unless symbol.declaration =~ /(?<=:).*?(?=(where|$))/ 106 | 107 | Regexp.last_match[0] =~ /\b#{protocol}\b/ 108 | end 109 | 110 | # Generate the 'where' clause for the declaration 111 | def where_clause 112 | parent_constraints = parent&.constraints || [] 113 | (constraints - parent_constraints).to_where_clause 114 | end 115 | 116 | def inherits_clause 117 | return '' unless superclass_name 118 | 119 | " : #{superclass_name}" 120 | end 121 | 122 | # approximately... 123 | def async? 124 | symbol.declaration =~ /\basync\b[^)]*$/ 125 | end 126 | 127 | def full_declaration 128 | symbol.attributes 129 | .append(symbol.declaration + inherits_clause + where_clause) 130 | .join("\n") 131 | end 132 | 133 | def to_sourcekit(module_name) 134 | declaration = full_declaration 135 | xml_declaration = "#{CGI.escapeHTML(declaration)}" 136 | 137 | hash = { 138 | 'key.kind' => symbol.kind, 139 | 'key.usr' => symbol.usr, 140 | 'key.name' => symbol.name, 141 | 'key.modulename' => module_name, 142 | 'key.parsed_declaration' => declaration, 143 | 'key.annotated_decl' => xml_declaration, 144 | 'key.symgraph_async' => async?, 145 | } 146 | if params = symbol.parameter_names 147 | hash['key.doc.parameters'] = 148 | params.map { |name| { 'name' => name } } 149 | end 150 | hash['key.symgraph_spi'] = true if symbol.spi 151 | 152 | add_children_to_sourcekit(hash, module_name) 153 | symbol.add_to_sourcekit(hash) 154 | end 155 | 156 | # Sort order - by symbol 157 | include Comparable 158 | 159 | def <=>(other) 160 | symbol <=> other.symbol 161 | end 162 | end 163 | end 164 | end 165 | -------------------------------------------------------------------------------- /lib/jazzy/grouper.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Jazzy 4 | # This module deals with arranging top-level declarations and guides into 5 | # groups automatically and/or using a custom list. 6 | module Grouper 7 | extend Config::Mixin 8 | 9 | # Group root-level docs by custom categories (if any) and type or module 10 | def self.group_docs(docs, doc_index) 11 | custom_categories, docs = group_custom_categories(docs, doc_index) 12 | unlisted_prefix = config.custom_categories_unlisted_prefix 13 | type_category_prefix = custom_categories.any? ? unlisted_prefix : '' 14 | all_categories = 15 | custom_categories + 16 | if config.merge_modules == :all 17 | group_docs_by_type(docs, type_category_prefix) 18 | else 19 | group_docs_by_module(docs, type_category_prefix) 20 | end 21 | merge_consecutive_marks(all_categories) 22 | end 23 | 24 | # Group root-level docs by type 25 | def self.group_docs_by_type(docs, type_category_prefix) 26 | type_groups = SourceDeclaration::Type.all.map do |type| 27 | children, docs = docs.partition { |doc| doc.type == type } 28 | make_type_group(children, type, type_category_prefix) 29 | end 30 | merge_categories(type_groups.compact) + docs 31 | end 32 | 33 | # Group root-level docs by module name 34 | def self.group_docs_by_module(docs, type_category_prefix) 35 | guide_categories, docs = group_guides(docs, type_category_prefix) 36 | 37 | module_categories = docs 38 | .group_by(&:doc_module_name) 39 | .map do |name, module_docs| 40 | make_group( 41 | module_docs, 42 | name, 43 | "The following declarations are provided by module #{name}.", 44 | ) 45 | end 46 | 47 | guide_categories + module_categories 48 | end 49 | 50 | def self.group_custom_categories(docs, doc_index) 51 | group = config.custom_categories.map do |category| 52 | children = category['children'].map do |selector| 53 | selected = select_docs(doc_index, selector) 54 | selected.map do |doc| 55 | unless doc.parent_in_code.nil? 56 | warn "WARNING: Declaration \"#{doc.fully_qualified_module_name}\" " \ 57 | 'specified in categories file exists but is not top-level and ' \ 58 | 'cannot be included here' 59 | next nil 60 | end 61 | docs.delete(doc) 62 | end 63 | end.flatten.compact 64 | # Category config overrides alphabetization 65 | children.each.with_index { |child, i| child.nav_order = i } 66 | make_group(children, category['name'], '') 67 | end 68 | [group.compact, docs] 69 | end 70 | 71 | def self.select_docs(doc_index, selector) 72 | if selector.is_a?(String) 73 | unless single_doc = doc_index.lookup(selector) 74 | warn 'WARNING: No documented top-level declarations match ' \ 75 | "name \"#{selector}\" specified in categories file" 76 | return [] 77 | end 78 | [single_doc] 79 | else 80 | doc_index.lookup_regex(selector['regex']) 81 | .sort_by(&:name) 82 | end 83 | end 84 | 85 | def self.group_guides(docs, prefix) 86 | guides, others = docs.partition { |doc| doc.type.markdown? } 87 | return [[], others] unless guides.any? 88 | 89 | [[make_type_group(guides, guides.first.type, prefix)], others] 90 | end 91 | 92 | def self.make_type_group(docs, type, type_category_prefix) 93 | make_group( 94 | docs, 95 | type_category_prefix + type.plural_name, 96 | "The following #{type.plural_name.downcase} are available globally.", 97 | type_category_prefix + type.plural_url_name, 98 | ) 99 | end 100 | 101 | # Join categories with the same name (eg. ObjC and Swift classes) 102 | def self.merge_categories(categories) 103 | merged = [] 104 | categories.each do |new_category| 105 | if existing = merged.find { |cat| cat.name == new_category.name } 106 | existing.children += new_category.children 107 | else 108 | merged.append(new_category) 109 | end 110 | end 111 | merged 112 | end 113 | 114 | def self.make_group(group, name, abstract, url_name = nil) 115 | group.reject! { |decl| decl.name.empty? } 116 | unless group.empty? 117 | SourceDeclaration.new.tap do |sd| 118 | sd.type = SourceDeclaration::Type.overview 119 | sd.name = name 120 | sd.url_name = url_name 121 | sd.abstract = Markdown.render(abstract) 122 | sd.children = group 123 | end 124 | end 125 | end 126 | 127 | # Merge consecutive sections with the same mark into one section 128 | # Needed because of pulling various decls into groups 129 | def self.merge_consecutive_marks(docs) 130 | prev_mark = nil 131 | docs.each do |doc| 132 | if prev_mark&.can_merge?(doc.mark) 133 | doc.mark = prev_mark 134 | end 135 | prev_mark = doc.mark 136 | merge_consecutive_marks(doc.children) 137 | end 138 | end 139 | end 140 | end 141 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | #-- Bootstrap --------------------------------------------------------------# 4 | 5 | desc 'Initializes your working copy to run the specs' 6 | task :bootstrap do 7 | if system('which bundle') 8 | title 'Installing gems' 9 | sh 'bundle install' 10 | 11 | title 'Updating submodules' 12 | sh 'git submodule update --init --recursive' 13 | else 14 | warn "\033[0;31m" \ 15 | "[!] Please install the bundler gem manually:\n" \ 16 | ' $ [sudo] gem install bundler' \ 17 | "\e[0m" 18 | exit 1 19 | end 20 | end 21 | 22 | begin 23 | require 'bundler/gem_tasks' 24 | require 'fileutils' 25 | 26 | task default: :spec 27 | 28 | #-- Specs ------------------------------------------------------------------# 29 | 30 | desc 'Run specs' 31 | task :spec do 32 | title 'Running Tests' 33 | Rake::Task['unit_spec'].invoke 34 | Rake::Task['objc_spec'].invoke 35 | Rake::Task['swift_spec'].invoke 36 | Rake::Task['cocoapods_spec'].invoke 37 | Rake::Task['rubocop'].invoke 38 | end 39 | 40 | desc 'Run unit specs' 41 | task :unit_spec do 42 | files = FileList['spec/*_spec.rb'] 43 | .exclude('spec/integration_spec.rb').shuffle.join(' ') 44 | sh "bundle exec bacon #{files}" 45 | end 46 | 47 | desc 'Run objc integration specs' 48 | task :objc_spec do 49 | sh 'JAZZY_SPEC_SUBSET=objc bundle exec bacon spec/integration_spec.rb' 50 | end 51 | 52 | desc 'Run swift integration specs' 53 | task :swift_spec do 54 | sh 'JAZZY_SPEC_SUBSET=swift bundle exec bacon spec/integration_spec.rb' 55 | end 56 | 57 | desc 'Run cocoapods integration specs' 58 | task :cocoapods_spec do 59 | sh 'JAZZY_SPEC_SUBSET=cocoapods bundle exec bacon spec/integration_spec.rb' 60 | end 61 | 62 | desc 'Rebuilds integration fixtures' 63 | task :rebuild_integration_fixtures do 64 | title 'Running Integration tests' 65 | sh 'rm -rf spec/integration_specs/tmp' 66 | puts `bundle exec bacon spec/integration_spec.rb` 67 | 68 | title 'Storing fixtures' 69 | # Copy the files to the files produced by the specs to the after folders 70 | FileList['tmp/*'].each do |source| 71 | destination = "spec/integration_specs/#{source.gsub('tmp/', '')}/after" 72 | if File.exist?(destination) 73 | sh "rm -rf #{destination}" 74 | sh "mv #{source}/transformed #{destination}" 75 | end 76 | end 77 | 78 | # Remove files not used for the comparison 79 | # To keep the git diff clean 80 | specs_root = 'spec/integration_specs/*/after' 81 | files_glob = "#{specs_root}/{*,.*}" 82 | files_to_delete = FileList[files_glob] 83 | .exclude('**/.', '**/..') 84 | .exclude("#{specs_root}/*docs", 85 | "#{specs_root}/execution_output.txt") 86 | .include("#{specs_root}/**/*.dsidx") 87 | .include("#{specs_root}/**/*.tgz") 88 | files_to_delete.each do |file_to_delete| 89 | sh "rm -rf '#{file_to_delete}'" 90 | end 91 | 92 | puts 93 | puts 'Integration fixtures updated, see `spec/integration_specs`' 94 | end 95 | 96 | #-- RuboCop ----------------------------------------------------------------# 97 | 98 | desc 'Runs RuboCop linter on Ruby files' 99 | task :rubocop do 100 | sh 'bundle exec rubocop' 101 | end 102 | 103 | #-- SourceKitten -----------------------------------------------------------# 104 | 105 | desc 'Vendors SourceKitten' 106 | task :sourcekitten do 107 | sk_dir = 'SourceKitten' 108 | bin_path = Dir.chdir(sk_dir) do 109 | build_cmd = 'swift build -c release --arch arm64 --arch x86_64' 110 | `#{build_cmd}` 111 | `#{build_cmd} --show-bin-path`.chomp 112 | end 113 | FileUtils.cp_r "#{bin_path}/sourcekitten", 'bin' 114 | end 115 | 116 | #-- Theme Dependencies -----------------------------------------------------# 117 | 118 | THEME_FILES = { 119 | 'jquery/dist/jquery.min.js' => [ 120 | 'themes/apple/assets/js', 121 | 'themes/fullwidth/assets/js', 122 | 'themes/jony/assets/js', 123 | ], 124 | 'lunr/lunr.min.js' => [ 125 | 'themes/apple/assets/js', 126 | 'themes/fullwidth/assets/js', 127 | ], 128 | 'corejs-typeahead/dist/typeahead.jquery.js' => [ 129 | 'themes/apple/assets/js', 130 | 'themes/fullwidth/assets/js', 131 | ], 132 | 'katex/dist/katex.min.css' => ['extensions/katex/css'], 133 | 'katex/dist/fonts' => ['extensions/katex/css'], 134 | 'katex/dist/katex.min.js' => ['extensions/katex/js'], 135 | }.freeze 136 | 137 | desc 'Copies theme dependencies (`npm update/install` by hand first)' 138 | task :theme_deps do 139 | THEME_FILES.each_pair do |src, dsts| 140 | dsts.each do |dst| 141 | FileUtils.cp_r "js/node_modules/#{src}", "lib/jazzy/#{dst}" 142 | end 143 | end 144 | end 145 | rescue LoadError, NameError => e 146 | warn "\033[0;31m" \ 147 | '[!] Some Rake tasks haven been disabled because the environment' \ 148 | ' couldn’t be loaded. Be sure to run `rake bootstrap` first.' \ 149 | "\e[0m" 150 | warn e.message 151 | warn e.backtrace 152 | warn '' 153 | end 154 | 155 | #-- Helpers ------------------------------------------------------------------# 156 | 157 | def title(title) 158 | cyan_title = "\033[0;36m#{title}\033[0m" 159 | puts 160 | puts '-' * 80 161 | puts cyan_title 162 | puts '-' * 80 163 | puts 164 | end 165 | -------------------------------------------------------------------------------- /lib/jazzy/podspec_documenter.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'tmpdir' 4 | require 'json' 5 | 6 | module Jazzy 7 | # rubocop:disable Metrics/ClassLength 8 | class PodspecDocumenter 9 | attr_reader :podspec 10 | 11 | def initialize(podspec) 12 | @podspec = podspec 13 | end 14 | 15 | # Build documentation from the given options 16 | # @param [Config] options 17 | def sourcekitten_output(config) 18 | installation_root = Pathname(Dir.mktmpdir(['jazzy', podspec.name])) 19 | installation_root.rmtree if installation_root.exist? 20 | Pod::Config.instance.with_changes(installation_root: installation_root, 21 | verbose: false) do 22 | sandbox = Pod::Sandbox.new(Pod::Config.instance.sandbox_root) 23 | installer = Pod::Installer.new(sandbox, podfile(config)) 24 | installer.install! 25 | stdout = Dir.chdir(sandbox.root) do 26 | targets = installer.pod_targets 27 | .select { |pt| pt.pod_name == podspec.root.name } 28 | .map(&:label) 29 | 30 | targets.map do |t| 31 | args = %W[doc --module-name #{podspec.module_name} -- -target #{t}] 32 | SourceKitten.run_sourcekitten(args) 33 | end 34 | end 35 | stdout.reduce([]) { |a, s| a + JSON.parse(s) }.to_json 36 | end 37 | end 38 | 39 | def self.create_podspec(podspec_path) 40 | case podspec_path 41 | when Pathname, String 42 | require 'cocoapods' 43 | Pod::Specification.from_file(podspec_path) 44 | end 45 | end 46 | 47 | # rubocop:disable Metrics/MethodLength 48 | def self.apply_config_defaults(podspec, config) 49 | return unless podspec 50 | 51 | unless config.author_name_configured 52 | config.author_name = author_name(podspec) 53 | config.author_name_configured = true 54 | end 55 | unless config.module_name_known? 56 | config.module_name = podspec.module_name 57 | config.module_name_configured = true 58 | end 59 | unless config.author_url_configured 60 | config.author_url = podspec.homepage || github_file_prefix(podspec) 61 | config.author_url_configured = true 62 | end 63 | unless config.version_configured 64 | config.version = podspec.version.to_s 65 | config.version_configured = true 66 | end 67 | unless config.source_host_files_url_configured 68 | config.source_host_files_url = github_file_prefix(podspec) 69 | config.source_host_files_url_configured = true 70 | end 71 | unless config.swift_version_configured 72 | trunk_swift_build = podspec.attributes_hash['pushed_with_swift_version'] 73 | config.swift_version = trunk_swift_build if trunk_swift_build 74 | config.swift_version_configured = true 75 | end 76 | end 77 | # rubocop:enable Metrics/MethodLength 78 | 79 | private 80 | 81 | # @!group Config helper methods 82 | 83 | def self.author_name(podspec) 84 | if podspec.authors.respond_to? :to_hash 85 | podspec.authors.keys.to_sentence || '' 86 | elsif podspec.authors.respond_to? :to_ary 87 | podspec.authors.to_sentence 88 | end || podspec.authors || '' 89 | end 90 | 91 | private_class_method :author_name 92 | 93 | def self.github_file_prefix(podspec) 94 | return unless podspec.source[:url] =~ %r{github.com[:/]+(.+)/(.+)} 95 | 96 | org, repo = Regexp.last_match 97 | return unless rev = podspec.source[:tag] || podspec.source[:commit] 98 | 99 | "https://github.com/#{org}/#{repo.sub(/\.git$/, '')}/blob/#{rev}" 100 | end 101 | 102 | private_class_method :github_file_prefix 103 | 104 | # Latest valid value for SWIFT_VERSION. 105 | LATEST_SWIFT_VERSION = '6' 106 | private_constant :LATEST_SWIFT_VERSION 107 | 108 | # All valid values for SWIFT_VERSION that are longer 109 | # than a major version number. Ordered ascending. 110 | LONG_SWIFT_VERSIONS = ['4.2'].freeze 111 | private_constant :LONG_SWIFT_VERSIONS 112 | 113 | # Go from a full Swift version like 4.2.1 to 114 | # something valid for SWIFT_VERSION. 115 | def compiler_swift_version(user_version) 116 | unless user_version 117 | return podspec_swift_version || LATEST_SWIFT_VERSION 118 | end 119 | 120 | LONG_SWIFT_VERSIONS.select do |version| 121 | user_version.start_with?(version) 122 | end.last || "#{user_version[0]}.0" 123 | end 124 | 125 | def podspec_swift_version 126 | # `swift_versions` exists from CocoaPods 1.7 127 | if podspec.respond_to?('swift_versions') 128 | podspec.swift_versions.max 129 | else 130 | podspec.swift_version 131 | end 132 | end 133 | 134 | # @!group SourceKitten output helper methods 135 | 136 | def pod_path 137 | if podspec.defined_in_file 138 | podspec.defined_in_file.parent 139 | else 140 | config.source_directory 141 | end 142 | end 143 | 144 | # rubocop:disable Metrics/MethodLength 145 | def podfile(config) 146 | swift_version = compiler_swift_version(config.swift_version) 147 | podspec = @podspec 148 | path = pod_path 149 | @podfile ||= Pod::Podfile.new do 150 | config.pod_sources.each do |src| 151 | source src 152 | end 153 | 154 | install! 'cocoapods', 155 | integrate_targets: false, 156 | deterministic_uuids: false 157 | 158 | [podspec, *podspec.recursive_subspecs].each do |ss| 159 | next if ss.test_specification 160 | 161 | ss.available_platforms.each do |p| 162 | # Travis builds take too long when building docs for all available 163 | # platforms for the Moya integration spec, so we just document OSX. 164 | # TODO: remove once jazzy is fast enough. 165 | next if ENV['JAZZY_INTEGRATION_SPECS'] && p.name != :osx 166 | 167 | target("Jazzy-#{ss.name.gsub('/', '__')}-#{p.name}") do 168 | use_frameworks! 169 | platform p.name, p.deployment_target 170 | pod ss.name, path: path.realpath.to_s 171 | current_target_definition.swift_version = swift_version 172 | end 173 | end 174 | end 175 | end 176 | end 177 | # rubocop:enable Metrics/MethodLength 178 | end 179 | # rubocop:enable Metrics/ClassLength 180 | end 181 | -------------------------------------------------------------------------------- /lib/jazzy/doc_index.rb: -------------------------------------------------------------------------------- 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 | # Look up of a regex matching all children for current level only. 68 | def lookup_regex(regex) 69 | children.select { |name, _| name.match(regex) } 70 | .map { |_, scope| scope.decl }.compact 71 | end 72 | 73 | # Get an array of scopes matching the name parts. 74 | def lookup_path(parts) 75 | [self] + 76 | (children[parts.first]&.lookup_path(parts[1...]) || []) 77 | end 78 | end 79 | 80 | attr_reader :root_scope 81 | 82 | def initialize(all_decls) 83 | @root_scope = Scope.new_root(all_decls.group_by(&:module_name)) 84 | end 85 | 86 | # Look up a name and return the matching SourceDeclaration or nil. 87 | # 88 | # `context` is an optional SourceDeclaration indicating where the text 89 | # was found, affects name resolution - see `lookup_context()` below. 90 | def lookup(name, context = nil) 91 | lookup_name = LookupName.new(name) 92 | 93 | return lookup_fully_qualified(lookup_name) if lookup_name.fully_qualified? 94 | return lookup_guess(lookup_name) if context.nil? 95 | 96 | lookup_context(lookup_name, context) 97 | end 98 | 99 | # Look up a regex and return all matching top level SourceDeclaration. 100 | def lookup_regex(regex) 101 | root_scope.children.map { |_, scope| scope.lookup_regex(regex) }.flatten 102 | end 103 | 104 | private 105 | 106 | # Look up a fully-qualified name, ie. it starts with the module name. 107 | def lookup_fully_qualified(lookup_name) 108 | root_scope.lookup(lookup_name.parts) 109 | end 110 | 111 | # Look up a top-level name best-effort, searching for a module that 112 | # has it before trying the first name-part as a module name. 113 | def lookup_guess(lookup_name) 114 | root_scope.children.each_value do |module_scope| 115 | if result = module_scope.lookup(lookup_name.parts) 116 | return result 117 | end 118 | end 119 | 120 | lookup_fully_qualified(lookup_name) 121 | end 122 | 123 | # Look up a name from a declaration context, approximately how 124 | # Swift resolves names. 125 | # 126 | # 1 - try and resolve with a common prefix, eg. 'B' from 'T.A' 127 | # can match 'T.B', or 'R' from 'S.T.A' can match 'S.R'. 128 | # 2 - try and resolve as a top-level symbol from a different module 129 | # 3 - (affordance for docs writers) resolve as a child of the context, 130 | # eg. 'B' from 'T.A' can match 'T.A.B' *only if* (1,2) fail. 131 | # Currently disabled for Swift for back-compatibility. 132 | def lookup_context(lookup_name, context) 133 | context_scope_path = 134 | root_scope.lookup_path(context.fully_qualified_module_name_parts) 135 | 136 | context_scope = context_scope_path.pop 137 | context_scope_path.reverse.each do |scope| 138 | if decl = scope.lookup(lookup_name.parts) 139 | return decl 140 | end 141 | end 142 | 143 | lookup_guess(lookup_name) || 144 | (lookup_name.objc? && context_scope.lookup(lookup_name.parts)) 145 | end 146 | 147 | # Helper for name lookup, really a cache for information as we 148 | # try various strategies. 149 | class LookupName 150 | attr_reader :name 151 | 152 | def initialize(name) 153 | @name = name 154 | end 155 | 156 | def fully_qualified? 157 | name.start_with?('/') 158 | end 159 | 160 | def objc? 161 | name.start_with?('-', '+') 162 | end 163 | 164 | def parts 165 | @parts ||= find_parts 166 | end 167 | 168 | private 169 | 170 | # Turn a name as written into a list of components to 171 | # be matched. 172 | # Swift: Strip out odd characters and split 173 | # ObjC: Compound names look like '+[Class(Category) method:]' 174 | # and need to become ['Class(Category)', '+method:'] 175 | def find_parts 176 | if name =~ /([+-])\[(\w+(?: ?\(\w+\))?) ([\w:]+)\]/ 177 | [Regexp.last_match[2], 178 | Regexp.last_match[1] + Regexp.last_match[3]] 179 | else 180 | name 181 | .sub(%r{^[@/]}, '') # ignore custom attribute reference, fully-qualified 182 | .gsub(/<.*?>/, '') # remove generic parameters 183 | .split(%r{(? 3.1) 7 | diffy 8 | 9 | PATH 10 | remote: . 11 | specs: 12 | jazzy (0.15.4) 13 | activesupport (>= 5.0, < 8) 14 | cocoapods (~> 1.5) 15 | logger 16 | mustache (~> 1.1) 17 | open4 (~> 1.3) 18 | redcarpet (~> 3.4) 19 | rexml (>= 3.2.7, < 4.0) 20 | rouge (>= 2.0.6, < 5.0) 21 | sassc (~> 2.1) 22 | sqlite3 (~> 1.3) 23 | xcinvoke (~> 0.3.0) 24 | 25 | GEM 26 | remote: https://rubygems.org/ 27 | specs: 28 | CFPropertyList (3.0.7) 29 | base64 30 | nkf 31 | rexml 32 | activesupport (7.2.2.2) 33 | base64 34 | benchmark (>= 0.3) 35 | bigdecimal 36 | concurrent-ruby (~> 1.0, >= 1.3.1) 37 | connection_pool (>= 2.2.5) 38 | drb 39 | i18n (>= 1.6, < 2) 40 | logger (>= 1.4.2) 41 | minitest (>= 5.1) 42 | securerandom (>= 0.3) 43 | tzinfo (~> 2.0, >= 2.0.5) 44 | addressable (2.8.7) 45 | public_suffix (>= 2.0.2, < 7.0) 46 | algoliasearch (1.27.5) 47 | httpclient (~> 2.8, >= 2.8.3) 48 | json (>= 1.5.1) 49 | ast (2.4.3) 50 | atomos (0.1.3) 51 | bacon (1.2.0) 52 | base64 (0.3.0) 53 | benchmark (0.4.1) 54 | bigdecimal (3.3.1) 55 | claide (1.1.0) 56 | claide-plugins (0.9.2) 57 | cork 58 | nap 59 | open4 (~> 1.3) 60 | cocoapods (1.16.2) 61 | addressable (~> 2.8) 62 | claide (>= 1.0.2, < 2.0) 63 | cocoapods-core (= 1.16.2) 64 | cocoapods-deintegrate (>= 1.0.3, < 2.0) 65 | cocoapods-downloader (>= 2.1, < 3.0) 66 | cocoapods-plugins (>= 1.0.0, < 2.0) 67 | cocoapods-search (>= 1.0.0, < 2.0) 68 | cocoapods-trunk (>= 1.6.0, < 2.0) 69 | cocoapods-try (>= 1.1.0, < 2.0) 70 | colored2 (~> 3.1) 71 | escape (~> 0.0.4) 72 | fourflusher (>= 2.3.0, < 3.0) 73 | gh_inspector (~> 1.0) 74 | molinillo (~> 0.8.0) 75 | nap (~> 1.0) 76 | ruby-macho (>= 2.3.0, < 3.0) 77 | xcodeproj (>= 1.27.0, < 2.0) 78 | cocoapods-core (1.16.2) 79 | activesupport (>= 5.0, < 8) 80 | addressable (~> 2.8) 81 | algoliasearch (~> 1.0) 82 | concurrent-ruby (~> 1.1) 83 | fuzzy_match (~> 2.0.4) 84 | nap (~> 1.0) 85 | netrc (~> 0.11) 86 | public_suffix (~> 4.0) 87 | typhoeus (~> 1.0) 88 | cocoapods-deintegrate (1.0.5) 89 | cocoapods-downloader (2.1) 90 | cocoapods-plugins (1.0.0) 91 | nap 92 | cocoapods-search (1.0.1) 93 | cocoapods-trunk (1.6.0) 94 | nap (>= 0.8, < 2.0) 95 | netrc (~> 0.11) 96 | cocoapods-try (1.2.0) 97 | colored2 (3.1.2) 98 | concurrent-ruby (1.3.5) 99 | connection_pool (2.5.4) 100 | cork (0.3.0) 101 | colored2 (~> 3.1) 102 | crack (1.0.0) 103 | bigdecimal 104 | rexml 105 | danger (9.5.3) 106 | base64 (~> 0.2) 107 | claide (~> 1.0) 108 | claide-plugins (>= 0.9.2) 109 | colored2 (>= 3.1, < 5) 110 | cork (~> 0.1) 111 | faraday (>= 0.9.0, < 3.0) 112 | faraday-http-cache (~> 2.0) 113 | git (>= 1.13, < 3.0) 114 | kramdown (>= 2.5.1, < 3.0) 115 | kramdown-parser-gfm (~> 1.0) 116 | octokit (>= 4.0) 117 | pstore (~> 0.1) 118 | terminal-table (>= 1, < 5) 119 | diffy (3.4.4) 120 | drb (2.2.3) 121 | escape (0.0.4) 122 | ethon (0.15.0) 123 | ffi (>= 1.15.0) 124 | faraday (2.14.0) 125 | faraday-net_http (>= 2.0, < 3.5) 126 | json 127 | logger 128 | faraday-http-cache (2.5.1) 129 | faraday (>= 0.8) 130 | faraday-net_http (3.4.1) 131 | net-http (>= 0.5.0) 132 | ffi (1.17.2) 133 | fourflusher (2.3.1) 134 | fuzzy_match (2.0.4) 135 | gh_inspector (1.1.3) 136 | git (2.3.3) 137 | activesupport (>= 5.0) 138 | addressable (~> 2.8) 139 | process_executer (~> 1.1) 140 | rchardet (~> 1.8) 141 | hashdiff (1.2.1) 142 | httpclient (2.9.0) 143 | mutex_m 144 | i18n (1.14.7) 145 | concurrent-ruby (~> 1.0) 146 | json (2.15.1) 147 | kramdown (2.5.1) 148 | rexml (>= 3.3.9) 149 | kramdown-parser-gfm (1.1.0) 150 | kramdown (~> 2.0) 151 | language_server-protocol (3.17.0.5) 152 | liferaft (0.0.6) 153 | lint_roller (1.1.0) 154 | logger (1.7.0) 155 | mini_portile2 (2.8.9) 156 | minitest (5.26.0) 157 | mocha (2.7.1) 158 | ruby2_keywords (>= 0.0.5) 159 | mocha-on-bacon (0.2.3) 160 | mocha (>= 0.13.0) 161 | molinillo (0.8.0) 162 | mustache (1.1.1) 163 | mutex_m (0.3.0) 164 | nanaimo (0.4.0) 165 | nap (1.1.0) 166 | net-http (0.6.0) 167 | uri 168 | netrc (0.11.0) 169 | nkf (0.2.0) 170 | octokit (10.0.0) 171 | faraday (>= 1, < 3) 172 | sawyer (~> 0.9) 173 | open4 (1.3.4) 174 | parallel (1.27.0) 175 | parser (3.3.9.0) 176 | ast (~> 2.4.1) 177 | racc 178 | prettybacon (0.0.2) 179 | bacon (~> 1.2) 180 | prism (1.6.0) 181 | process_executer (1.3.0) 182 | pstore (0.2.0) 183 | public_suffix (4.0.7) 184 | racc (1.8.1) 185 | rainbow (3.1.1) 186 | rake (13.3.0) 187 | rchardet (1.10.0) 188 | redcarpet (3.6.1) 189 | regexp_parser (2.11.3) 190 | rexml (3.4.4) 191 | rouge (4.6.1) 192 | rubocop (1.81.1) 193 | json (~> 2.3) 194 | language_server-protocol (~> 3.17.0.2) 195 | lint_roller (~> 1.1.0) 196 | parallel (~> 1.10) 197 | parser (>= 3.3.0.2) 198 | rainbow (>= 2.2.2, < 4.0) 199 | regexp_parser (>= 2.9.3, < 3.0) 200 | rubocop-ast (>= 1.47.1, < 2.0) 201 | ruby-progressbar (~> 1.7) 202 | unicode-display_width (>= 2.4.0, < 4.0) 203 | rubocop-ast (1.47.1) 204 | parser (>= 3.3.7.2) 205 | prism (~> 1.4) 206 | ruby-macho (2.5.1) 207 | ruby-progressbar (1.13.0) 208 | ruby2_keywords (0.0.5) 209 | sassc (2.4.0) 210 | ffi (~> 1.9) 211 | sawyer (0.9.2) 212 | addressable (>= 2.3.5) 213 | faraday (>= 0.17.3, < 3) 214 | securerandom (0.4.1) 215 | sqlite3 (1.7.3) 216 | mini_portile2 (~> 2.8.0) 217 | terminal-table (4.0.0) 218 | unicode-display_width (>= 1.1.1, < 4) 219 | typhoeus (1.5.0) 220 | ethon (>= 0.9.0, < 0.16.0) 221 | tzinfo (2.0.6) 222 | concurrent-ruby (~> 1.0) 223 | unicode-display_width (3.2.0) 224 | unicode-emoji (~> 4.1) 225 | unicode-emoji (4.1.0) 226 | uri (1.0.4) 227 | webmock (3.25.1) 228 | addressable (>= 2.8.0) 229 | crack (>= 0.3.2) 230 | hashdiff (>= 0.4.0, < 2.0.0) 231 | xcinvoke (0.3.0) 232 | liferaft (~> 0.0.6) 233 | xcodeproj (1.27.0) 234 | CFPropertyList (>= 2.3.3, < 4.0) 235 | atomos (~> 0.1.3) 236 | claide (>= 1.0.2, < 2.0) 237 | colored2 (~> 3.1) 238 | nanaimo (~> 0.4.0) 239 | rexml (>= 3.3.6, < 4.0) 240 | 241 | PLATFORMS 242 | ruby 243 | 244 | DEPENDENCIES 245 | bacon 246 | bundler (~> 2.1) 247 | clintegracon! 248 | danger 249 | diffy 250 | jazzy! 251 | mocha 252 | mocha-on-bacon 253 | prettybacon 254 | rake (~> 13.0) 255 | rubocop (~> 1.18) 256 | webmock 257 | 258 | BUNDLED WITH 259 | 2.3.26 260 | -------------------------------------------------------------------------------- /lib/jazzy/symbol_graph/graph.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # rubocop:disable Metrics/ClassLength 4 | module Jazzy 5 | module SymbolGraph 6 | # A Graph is the coordinator to import a symbolgraph json file. 7 | # Deserialize it to Symbols and Relationships, then rebuild 8 | # the AST shape using SymNodes and ExtNodes and extract SourceKit json. 9 | class Graph 10 | attr_accessor :module_name # Our module 11 | attr_accessor :ext_module_name # Module being extended 12 | attr_accessor :symbol_nodes # usr -> SymNode 13 | attr_accessor :relationships # [Relationship] 14 | attr_accessor :ext_nodes # (usr, constraints) -> ExtNode 15 | 16 | # Parse the JSON into flat tables of data 17 | def initialize(json, module_name, ext_module_name) 18 | self.module_name = module_name 19 | self.ext_module_name = ext_module_name 20 | graph = JSON.parse(json, symbolize_names: true) 21 | 22 | self.symbol_nodes = {} 23 | self.ext_nodes = {} 24 | 25 | graph[:symbols].each do |hash| 26 | symbol = Symbol.new(hash) 27 | if symbol.extension? 28 | node = ExtSymNode.new(symbol) 29 | ext_nodes[node.ext_key] = node 30 | else 31 | symbol_nodes[symbol.usr] = SymNode.new(symbol) 32 | end 33 | end 34 | 35 | self.relationships = 36 | graph[:relationships].map { |hash| Relationship.new(hash) } 37 | end 38 | 39 | # ExtNode index. ExtKey (type USR, extension constraints) -> ExtNode. 40 | # This minimizes the number of extensions 41 | 42 | def add_ext_member(type_usr, member_node, constraints) 43 | key = ExtKey.new(type_usr, constraints.ext) 44 | if ext_node = ext_nodes[key] 45 | ext_node.add_child(member_node) 46 | else 47 | ext_nodes[key] = 48 | ExtNode.new_for_member(type_usr, member_node, constraints) 49 | end 50 | end 51 | 52 | def add_ext_conformance(type_usr, 53 | type_name, 54 | protocol, 55 | constraints) 56 | key = ExtKey.new(type_usr, constraints.ext) 57 | if ext_node = ext_nodes[key] 58 | ext_node.add_conformance(protocol) 59 | else 60 | ext_nodes[key] = 61 | ExtNode.new_for_conformance(type_usr, 62 | type_name, 63 | protocol, 64 | constraints) 65 | end 66 | end 67 | 68 | # Increasingly desparate ways to find the name of the symbol 69 | # at the target end of a relationship 70 | def rel_target_name(rel, target_node) 71 | target_node&.symbol&.name || 72 | rel.target_fallback || 73 | Jazzy::SymbolGraph.demangle(rel.target_usr) 74 | end 75 | 76 | # Same for the source end. Less help from the tool here 77 | def rel_source_name(rel, source_node) 78 | source_node&.qualified_name || 79 | Jazzy::SymbolGraph.demangle(rel.source_usr) 80 | end 81 | 82 | # Protocol conformance is redundant if it's unconditional 83 | # and already expressed in the type's declaration. 84 | # 85 | # Skip implementation-detail conformances. 86 | def redundant_conformance?(rel, type, protocol) 87 | return false unless type 88 | 89 | (rel.constraints.empty? && type.conformance?(protocol)) || 90 | (type.actor? && rel.actor_protocol?) 91 | end 92 | 93 | # source is a member/protocol requirement of target 94 | def rebuild_member(rel, source, target) 95 | return unless source 96 | 97 | source.protocol_requirement = rel.protocol_requirement? 98 | constraints = 99 | ExtConstraints.new(target&.constraints, 100 | source.unique_context_constraints(target)) 101 | 102 | # Add to its parent or invent an extension 103 | unless target&.add_child?(source, constraints.ext) 104 | add_ext_member(rel.target_usr, source, constraints) 105 | end 106 | end 107 | 108 | # "source : target" either from type decl or ext decl 109 | def rebuild_conformance(rel, source, target) 110 | protocol_name = rel_target_name(rel, target) 111 | 112 | return if redundant_conformance?(rel, source, protocol_name) 113 | 114 | type_constraints = source&.constraints || [] 115 | constraints = 116 | ExtConstraints.new(type_constraints, 117 | rel.constraints - type_constraints) 118 | 119 | # Create an extension or enhance an existing one 120 | add_ext_conformance(rel.source_usr, 121 | rel_source_name(rel, source), 122 | protocol_name, 123 | constraints) 124 | end 125 | 126 | # "source is a default implementation of protocol requirement target" 127 | def rebuild_default_implementation(_rel, source, target) 128 | return unless source 129 | 130 | unless target && 131 | (target_parent = target.parent) && 132 | target_parent.is_a?(SymNode) 133 | # Could probably figure this out with demangle, but... 134 | warn "Can't resolve membership of default implementation " \ 135 | "#{source.symbol.usr}." 136 | source.unlisted = true 137 | return 138 | end 139 | constraints = 140 | ExtConstraints.new(target_parent.constraints, 141 | source.unique_context_constraints(target_parent)) 142 | 143 | add_ext_member(target_parent.symbol.usr, 144 | source, 145 | constraints) 146 | end 147 | 148 | # "source is a class that inherits from target" 149 | def rebuild_inherits(_rel, source, target) 150 | if source && target 151 | source.superclass_name = target.symbol.name 152 | end 153 | end 154 | 155 | # "References to fake_usr should be real_usr" 156 | def unalias_extensions(fake_usr, real_usr) 157 | ext_nodes.each_pair do |key, ext| 158 | if key.usr == fake_usr 159 | ext.real_usr = real_usr 160 | end 161 | end 162 | end 163 | 164 | # Process a structural relationship to link nodes 165 | def rebuild_rel(rel) 166 | source = symbol_nodes[rel.source_usr] 167 | target = symbol_nodes[rel.target_usr] 168 | 169 | case rel.kind 170 | when :memberOf, :optionalRequirementOf, :requirementOf 171 | rebuild_member(rel, source, target) 172 | 173 | when :conformsTo 174 | rebuild_conformance(rel, source, target) 175 | 176 | when :defaultImplementationOf 177 | rebuild_default_implementation(rel, source, target) 178 | 179 | when :inheritsFrom 180 | rebuild_inherits(rel, source, target) 181 | 182 | when :extensionTo 183 | unalias_extensions(rel.source_usr, rel.target_usr) 184 | end 185 | 186 | # don't seem to care about: 187 | # - overrides: not bothered, also unimplemented for protocols 188 | end 189 | 190 | # Rebuild the AST structure and convert to SourceKit 191 | def to_sourcekit 192 | relationships.sort.each { |r| rebuild_rel(r) } 193 | 194 | root_symbol_nodes = 195 | symbol_nodes.values 196 | .select(&:top_level_decl?) 197 | .sort 198 | .map { |n| n.to_sourcekit(module_name) } 199 | 200 | root_ext_nodes = 201 | ext_nodes.values 202 | .sort 203 | .map { |n| n.to_sourcekit(module_name, ext_module_name) } 204 | { 205 | 'key.diagnostic_stage' => 'parse', 206 | 'key.substructure' => root_symbol_nodes + root_ext_nodes, 207 | } 208 | end 209 | end 210 | end 211 | end 212 | # rubocop:enable Metrics/ClassLength 213 | -------------------------------------------------------------------------------- /ObjectiveC.md: -------------------------------------------------------------------------------- 1 | This document is about Jazzy's Objective-C documentation generation. 2 | 3 | It's intended for users who are having problems after trying to follow the 4 | examples in the [README](README.md#objective-c). It gives some solutions to 5 | common problems and explains how the system works to help users work through 6 | uncommon problems. 7 | 8 | * [How it works](#how-objective-c-docs-generation-works) 9 | * Common problems: 10 | * [Apple SDK include failure](#problem-apple-sdk-importinclude-failure) 11 | * [Non-SDK include failure](#problem-non-sdk-include-failure) 12 | * [Argument list too long](#problem-argument-list-too-long-e2big-and-more) 13 | * [Enum cases with wrong doc comment](#problem-enum-cases-have-the-wrong-doc-comment) 14 | * [Swift API versions missing](#problem-swift-api-versions-are-all-missing) 15 | * [Swift API versions use `Any`](#problem-swift-api-versions-have-any-instead-of-type-name) 16 | * [Structural `NS_SWIFT_NAME` not working](#problem-structural-ns_swift_name-not-working) 17 | 18 | # How Objective-C docs generation works 19 | 20 | Jazzy uses `libclang` to generate docs for Objective-C projects. You can think 21 | of this as running some parts of the `clang` compiler against a header file. 22 | Jazzy refers to this header file as the _umbrella header_ but it does not have 23 | to be the umbrella header from a Clang module: it's just the header file that 24 | includes everything to be documented. 25 | 26 | This means there are two problems to solve: 27 | 1. What `clang` flags are required; and 28 | 2. How to pass them to the tools. 29 | 30 | Jazzy has two modes here: a smart mode that covers 90% of projects and a direct mode where the user provides all the flags. 31 | 32 | > *Important*: Jazzy does _not_ use any Objective-C build settings from your 33 | Xcode project or `Package.swift`. If your project needs special settings 34 | such as `#define`s then you need to repeat those in the Jazzy invocation. 35 | 36 | ## Direct mode 37 | 38 | Passing a basic set of `clang` flags looks like this: 39 | 40 | ```shell 41 | jazzy ... 42 | --objc 43 | --build-tool-arguments 44 | --objc,MyProject/MyProject.h,--,-x,objective-c, 45 | -isysroot,$(xcrun --show-sdk-path), 46 | -I,$(pwd), 47 | -fmodules 48 | ``` 49 | The `--build-tool-arguments` are arguments to `sourcekitten`. Everything after 50 | the `--` are the `clang` flags that will be used with the header file given 51 | before the `--`. 52 | 53 | You can try these flags outside of Jazzy's environment: 54 | ```shell 55 | clang -c -x objective-c -isysroot $(xcrun --show-sdk-path) -I $(pwd) MyProject/MyProject.h -fmodules 56 | ``` 57 | (The `-c` stops `clang` from trying to link an executable.) 58 | 59 | This is a good method of experimenting with compiler flags to get a working 60 | build without getting bogged down in the Jazzy and SourceKitten layers. 61 | 62 | ## Smart mode 63 | 64 | The smart mode takes the variable parts of the basic set of flags and maps 65 | them from Jazzy flags: 66 | ```shell 67 | jazzy ... 68 | --objc 69 | --umbrella-header MyProject/MyProject.h 70 | --framework-root $(pwd) 71 | [--sdk ] 72 | ``` 73 | 74 | The `--umbrella-header` maps directly to the file passed to `sourcekitten`. 75 | 76 | The `--framework-root` is used for the `-I` include path, as well as every 77 | directory beneath it, recursively. So if your project structure looks like: 78 | ``` 79 | MyProject/ 80 | Sources/ 81 | Main/ 82 | Extension/ 83 | ``` 84 | ... and you pass `--framework-root MyProject`, then the `-I` flags passed to 85 | `clang` are `-I MyProject -I MyProject/Sources -I MyProject/Sources/Main -I 86 | MyProject/Sources/Extension`. This feature helps some projects resolve 87 | `#include` directives. 88 | 89 | Finally the `--sdk` option is passed through instead of the default `macosx` to 90 | find the SDK. 91 | 92 | ## Mixing modes 93 | 94 | Do not mix modes. For example do not set both `--umbrella-header` and 95 | `--build-tool-arguments`. Jazzy does not flag this as an error for 96 | historical compatibility reasons, but the results are at best confusing. 97 | 98 | # Problem: Apple SDK import/include failure 99 | 100 | For example `fatal error: module 'UIKit' not found`. 101 | 102 | This means Jazzy is using the wrong SDK: the default is for macOS which does 103 | not have `UIKit`. Use `--sdk iphonesimulator`. 104 | 105 | # Problem: Non-SDK include failure 106 | 107 | For example `fatal error: 'MyModule/SomeHeader.h' file not found`. 108 | 109 | This means `clang` is not being passed the right flags to resolve a `#include` / 110 | `#import`. 111 | 112 | Start by finding the header file in the filesystem. You might be able to fix 113 | the problem just by adding extra `-I ` flags. 114 | 115 | Usually though the problem is that Xcode has done something clever that needs 116 | to be worked around or replicated. 117 | 118 | Xcode uses technology including Clang header maps to let files be found using 119 | lines like `#import ` even when there is no such 120 | filesystem directory. 121 | 122 | To make the Jazzy build work you need to make these `#include`s work. The 123 | solution depends on your project structure. Some suggestions in rough order 124 | of complexity: 125 | * Use symlinks to create an equivalent directory tree. For example if 126 | `Header.h` is inside `Sources/include` then symlink that directory to 127 | `ModuleName` and pass `-I $(pwd)`. 128 | 129 | * Copy/link your header files into a throwaway directory tree that matches 130 | the required structure and is used just for building docs. 131 | 132 | * Create a 'docs header file' separate to the framework's regular umbrella 133 | header that contains only filesystem-correct `#import`s. 134 | 135 | * If you are happy to build the framework project before generating docs and 136 | all the problematic paths have the form `ModuleName/PublicHeader.h` then 137 | have `clang` resolve those includes to the built framework by passing 138 | `-F `. 139 | 140 | * If you are happy to build the project before generating docs then you may 141 | be able to use the header maps Xcode has generated. Check the build log in 142 | Xcode to find them and the invocation syntax. 143 | 144 | * Manually construct an equivalent header map. This is complex not least 145 | because Apple does not make tools or proper documentation available. 146 | [An open-source starting point](https://milen.me/writings/swift-module-maps-vfs-overlays-header-maps/). 147 | 148 | # Problem: Argument list too long `E2BIG` (and more...) 149 | 150 | For example ``...open4.rb:49:in `exec': Argument list too long - [...]/bin/sourcekitten (Errno::E2BIG)`` 151 | 152 | Can also manifest as 'generally weird' errors such as `sourcekitten` just 153 | crashing and `fatal error: could not build module 'Foundation'`. 154 | 155 | This means `--framework-root` is trying to add too many include directories: 156 | there are too many subdirectories of its parameter. If you cannot change this 157 | to something more specific that works then you need to use Jazzy's 158 | [direct mode](#direct-mode) to pass in the correct directories. 159 | 160 | # Problem: Enum cases have the wrong doc comment 161 | 162 | If you write an enum case with a doc comment followed by an enum case without 163 | a doc comment, then both get the same doc comment. 164 | 165 | This seems to be a bug in `libclang`. The only workaround is to add the missing 166 | doc comment. 167 | 168 | # Problem: Swift API versions are all missing 169 | 170 | This usually means the `clang` flags are malformed in a way that is ignored by 171 | `libclang` but not by the Swift Objective-C importer. 172 | 173 | One easy way to accidentally do this is passing `-I` without a path, for 174 | example `--build-tool-flags ...,-I,-I,Headers`,.... 175 | 176 | This also sometimes happens if you are frequently switching back and forth 177 | between some Swift / Xcode versions -- it's a bug somewhere in the Apple tools. 178 | The bad state goes away with time / reboot. 179 | 180 | # Problem: Swift API versions have `Any` instead of type name 181 | 182 | Jazzy finds the Swift version of an Objective-C API using the SourceKit 183 | `source.request.editor.open.interface.header` request on the header file that 184 | contains the declaration, passing in the same `clang` flags used for the 185 | umbrella header. [See the code](https://github.com/jpsim/SourceKitten/blob/bed112c313ca8c3c149f8cb84069f1c080e86a7e/Source/SourceKittenFramework/Clang%2BSourceKitten.swift#L202). 186 | 187 | This means that each header file needs to compile standalone, without 188 | any implicit dependencies. For example: 189 | ``` 190 | MyModule.h // umbrella, includes MyClass.h then Utils.h 191 | MyClass.h // @interface MyClass ... @end 192 | Utils.h // void my_function( MyClass * myClass); 193 | ``` 194 | Here, `Utils.h` has an implicit dependency on `MyClass.h` that is normally 195 | satisfied by the include order of `MyModule.h`. One fix that allows `Utils.h` 196 | to compile standalone is to add `@class MyClass;`. 197 | 198 | # Problem: Structural `NS_SWIFT_NAME` not working 199 | 200 | The `NS_SWIFT_NAME` macro is mostly used to give an Objective-C API a 201 | different name in Swift. There are no known problems with this part. 202 | 203 | The macro can also be used to change the 'structure' of an API, for example 204 | make a C global function appear as a member function in Swift, or make a C 205 | class appear as a nested type in Swift. 206 | 207 | Jazzy doesn't understand or communicate these structural changes: you'll need 208 | to explain it in doc comments. 209 | -------------------------------------------------------------------------------- /lib/jazzy/source_declaration.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'jazzy/source_declaration/access_control_level' 4 | require 'jazzy/source_declaration/type' 5 | 6 | module Jazzy 7 | # rubocop:disable Metrics/ClassLength 8 | class SourceDeclaration 9 | # kind of declaration (e.g. class, variable, function) 10 | attr_accessor :type 11 | # static type of declared element (e.g. String.Type -> ()) 12 | attr_accessor :typename 13 | 14 | # Give the item its own page or just inline into parent? 15 | def render_as_page? 16 | children.any? || 17 | (Config.instance.separate_global_declarations && 18 | type.global?) 19 | end 20 | 21 | def swift? 22 | type.swift_type? 23 | end 24 | 25 | def highlight_language 26 | swift? ? Highlighter::SWIFT : Highlighter::OBJC 27 | end 28 | 29 | # When referencing this item from its parent category, 30 | # include the content or just link to it directly? 31 | def omit_content_from_parent? 32 | Config.instance.separate_global_declarations && 33 | render_as_page? 34 | end 35 | 36 | # Element containing this declaration in the code 37 | attr_accessor :parent_in_code 38 | 39 | # Logical parent in the documentation. May differ from parent_in_code 40 | # because of top-level categories and merged extensions. 41 | attr_accessor :parent_in_docs 42 | 43 | # counterpart of parent_in_docs 44 | attr_reader :children 45 | 46 | def children=(new_children) 47 | # Freeze to ensure that parent_in_docs stays in sync 48 | @children = new_children.freeze 49 | @children.each { |c| c.parent_in_docs = self } 50 | end 51 | 52 | # Chain of parent_in_code from top level to self. (Includes self.) 53 | def namespace_path 54 | namespace_ancestors + [self] 55 | end 56 | 57 | def namespace_ancestors 58 | if parent_in_code 59 | parent_in_code.namespace_path 60 | else 61 | [] 62 | end 63 | end 64 | 65 | # 'OuterType.NestedType.method(arg:)' 66 | def fully_qualified_name 67 | namespace_path.map(&:name).join('.') 68 | end 69 | 70 | # :name doesn't include any generic type params. 71 | # This regexp matches any generic type params in parent names. 72 | def fully_qualified_name_regexp 73 | Regexp.new(namespace_path.map(&:name) 74 | .map { |n| Regexp.escape(n) } 75 | .join('(?:<.*?>)?\.')) 76 | end 77 | 78 | def fully_qualified_module_name_parts 79 | path = namespace_path 80 | path.map(&:name).prepend(path.first.module_name).compact 81 | end 82 | 83 | # 'MyModule.OuterType.NestedType.method(arg:)' 84 | def fully_qualified_module_name 85 | fully_qualified_module_name_parts.join('.') 86 | end 87 | 88 | # List of doc_parent decls, .last is self 89 | def docs_path 90 | (parent_in_docs&.docs_path || []) + [self] 91 | end 92 | 93 | # If this declaration is an objc category, returns an array with the name 94 | # of the extended objc class and the category name itself, i.e. 95 | # ["NSString", "MyMethods"], nil otherwise. 96 | def objc_category_name 97 | name.split(/[()]/) if type.objc_category? 98 | end 99 | 100 | def swift_objc_extension? 101 | type.swift_extension? && usr&.start_with?('c:objc') 102 | end 103 | 104 | def swift_extension_objc_name 105 | return unless type.swift_extension? && usr 106 | 107 | usr.split('(cs)').last 108 | end 109 | 110 | # The language in the templates for display 111 | def display_language 112 | return 'Swift' if swift? 113 | 114 | Config.instance.hide_objc? ? 'Swift' : 'Objective-C' 115 | end 116 | 117 | def display_declaration 118 | return declaration if swift? 119 | 120 | Config.instance.hide_objc? ? other_language_declaration : declaration 121 | end 122 | 123 | def display_other_language_declaration 124 | other_language_declaration unless 125 | Config.instance.hide_objc? || Config.instance.hide_swift? 126 | end 127 | 128 | attr_accessor :file 129 | attr_accessor :line 130 | attr_accessor :column 131 | attr_accessor :usr 132 | attr_accessor :type_usr 133 | attr_accessor :module_name 134 | attr_accessor :name 135 | attr_accessor :objc_name 136 | attr_accessor :declaration 137 | attr_accessor :other_language_declaration 138 | attr_accessor :abstract 139 | attr_accessor :default_impl_abstract 140 | attr_accessor :from_protocol_extension 141 | attr_accessor :discussion 142 | attr_accessor :return 143 | attr_accessor :parameters 144 | attr_accessor :url 145 | attr_accessor :mark 146 | attr_accessor :access_control_level 147 | attr_accessor :start_line 148 | attr_accessor :end_line 149 | attr_accessor :nav_order 150 | attr_accessor :url_name 151 | attr_accessor :deprecated 152 | attr_accessor :deprecation_message 153 | attr_accessor :unavailable 154 | attr_accessor :unavailable_message 155 | attr_accessor :generic_requirements 156 | attr_accessor :inherited_types 157 | attr_accessor :async 158 | 159 | # The name of the module being documented that contains this 160 | # declaration. Only different from module_name when this is 161 | # an extension of a type from another module. Nil for guides. 162 | attr_accessor :doc_module_name 163 | 164 | def usage_discouraged? 165 | unavailable || deprecated 166 | end 167 | 168 | def filepath 169 | CGI.unescape(url) 170 | end 171 | 172 | # Base filename (no extension) for the item 173 | def docs_filename 174 | result = url_name || name 175 | # Workaround functions sharing names with 176 | # different argument types (f(a:Int) vs. f(a:String)) 177 | return result unless type.swift_global_function? 178 | 179 | result + "_#{type_usr}" 180 | end 181 | 182 | def constrained_extension? 183 | type.swift_extension? && 184 | generic_requirements 185 | end 186 | 187 | def mark_for_children 188 | if constrained_extension? 189 | SourceMark.new_generic_requirements(generic_requirements) 190 | else 191 | SourceMark.new 192 | end 193 | end 194 | 195 | def inherited_types? 196 | inherited_types && 197 | !inherited_types.empty? 198 | end 199 | 200 | # Is there at least one inherited type that is not in the given list? 201 | def other_inherited_types?(unwanted) 202 | return false unless inherited_types? 203 | 204 | inherited_types.any? { |t| !unwanted.include?(t) } 205 | end 206 | 207 | # Pre-Swift 5.6: SourceKit only sets module_name for imported modules 208 | # Swift 5.6+: module_name is always set 209 | def type_from_doc_module? 210 | !type.extension? || 211 | (swift? && usr && 212 | (module_name.nil? || module_name == doc_module_name)) 213 | end 214 | 215 | # Don't ask the user to write documentation for types being extended 216 | # from other modules. Compile errors leave no docs and a `nil` USR. 217 | def mark_undocumented? 218 | !swift? || (usr && !extension_of_external_type?) 219 | end 220 | 221 | def extension_of_external_type? 222 | !module_name.nil? && 223 | !Config.instance.module_name?(module_name) 224 | end 225 | 226 | # Is it unclear from context what module the (top-level) decl is from? 227 | def ambiguous_module_name?(group_name) 228 | extension_of_external_type? || 229 | (Config.instance.multiple_modules? && 230 | !module_name.nil? && 231 | group_name != module_name) 232 | end 233 | 234 | # Does the user need help understanding how to get this declaration? 235 | def need_doc_module_note? 236 | return false unless Config.instance.multiple_modules? 237 | return false if docs_path.first.name == doc_module_name 238 | 239 | if parent_in_code.nil? 240 | # Top-level decls with no page of their own 241 | !render_as_page? 242 | else 243 | # Members added by extension 244 | parent_in_code.module_name != doc_module_name 245 | end 246 | end 247 | 248 | # Info text for contents page by collapsed item name 249 | def declaration_note 250 | notes = [ 251 | default_impl_abstract ? 'default implementation' : nil, 252 | from_protocol_extension ? 'extension method' : nil, 253 | async ? 'asynchronous' : nil, 254 | need_doc_module_note? ? "from #{doc_module_name}" : nil, 255 | ].compact 256 | notes.join(', ').upcase_first unless notes.empty? 257 | end 258 | 259 | # For matching where `Self` is equivalent without considering 260 | # constraints 261 | def simplified_typename 262 | typename&.gsub(//, '') 263 | end 264 | 265 | # Is the candidate `SourceDeclaration` probably a default 266 | # implementation of this declaration? 267 | def default_implementation?(candidate) 268 | name == candidate.name && 269 | type == candidate.type && 270 | simplified_typename == candidate.simplified_typename && 271 | async == candidate.async 272 | end 273 | 274 | def readme? 275 | false 276 | end 277 | 278 | def alternative_abstract 279 | if file = alternative_abstract_file 280 | Pathname(file).read 281 | end 282 | end 283 | 284 | def alternative_abstract_file 285 | abstract_glob.select do |f| 286 | # allow Structs.md or Structures.md 287 | [name, url_name].include?(File.basename(f).split('.').first) 288 | end.first 289 | end 290 | 291 | def abstract_glob 292 | return [] unless 293 | Config.instance.abstract_glob_configured && 294 | Config.instance.abstract_glob 295 | 296 | Config.instance.abstract_glob.select { |e| File.file? e } 297 | end 298 | end 299 | # rubocop:enable Metrics/ClassLength 300 | end 301 | -------------------------------------------------------------------------------- /lib/jazzy/symbol_graph/symbol.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # rubocop:disable Metrics/ClassLength 4 | module Jazzy 5 | module SymbolGraph 6 | # A Symbol is a tidied-up SymbolGraph JSON object 7 | class Symbol 8 | attr_accessor :usr 9 | attr_accessor :path_components 10 | attr_accessor :declaration 11 | attr_accessor :kind 12 | attr_accessor :acl 13 | attr_accessor :spi 14 | attr_accessor :location # can be nil, keys :filename :line :character 15 | attr_accessor :constraints # array, can be empty 16 | attr_accessor :doc_comments # can be nil 17 | attr_accessor :attributes # array, can be empty 18 | attr_accessor :generic_type_params # set, can be empty 19 | attr_accessor :parameter_names # array, can be nil 20 | 21 | def name 22 | path_components[-1] || '??' 23 | end 24 | 25 | def full_name 26 | path_components.join('.') 27 | end 28 | 29 | def initialize(hash) 30 | self.usr = hash[:identifier][:precise] 31 | self.path_components = hash[:pathComponents] 32 | raw_decl, keywords = parse_decl_fragments(hash[:declarationFragments]) 33 | init_kind(hash[:kind][:identifier], keywords) 34 | init_declaration(raw_decl) 35 | if func_signature = hash[:functionSignature] 36 | init_func_signature(func_signature) 37 | end 38 | init_acl(hash[:accessLevel]) 39 | self.spi = hash[:spi] 40 | if location = hash[:location] 41 | init_location(location) 42 | end 43 | init_constraints(hash, raw_decl) 44 | if comments_hash = hash[:docComment] 45 | init_doc_comments(comments_hash) 46 | end 47 | init_attributes(hash[:availability] || []) 48 | init_generic_type_params(hash) 49 | end 50 | 51 | def parse_decl_fragments(fragments) 52 | decl = '' 53 | keywords = Set.new 54 | fragments.each do |frag| 55 | decl += frag[:spelling] 56 | keywords.add(frag[:spelling]) if frag[:kind] == 'keyword' 57 | end 58 | [decl, keywords] 59 | end 60 | 61 | # Repair problems with SymbolGraph's declprinter 62 | 63 | def init_declaration(raw_decl) 64 | # Too much 'Self.TypeName'; omitted arg labels look odd; 65 | # duplicated constraints; swift 5.3 vs. master workaround 66 | self.declaration = raw_decl 67 | .gsub(/\bSelf\./, '') 68 | .gsub(/(?<=\(|, )_: /, '_ arg: ') 69 | .gsub(/ where.*$/, '') 70 | if kind == 'source.lang.swift.decl.class' 71 | declaration.sub!(/\s*:.*$/, '') 72 | end 73 | end 74 | 75 | # Remember pieces of methods for later markdown parsing 76 | 77 | def init_func_signature(func_signature) 78 | self.parameter_names = 79 | (func_signature[:parameters] || []).map { |h| h[:name] } 80 | end 81 | 82 | # Mapping SymbolGraph's declkinds to SourceKit 83 | 84 | KIND_MAP = { 85 | 'class' => 'class', 86 | 'struct' => 'struct', 87 | 'enum' => 'enum', 88 | 'enum.case' => 'enumelement', # intentional 89 | 'protocol' => 'protocol', 90 | 'init' => 'function.constructor', 91 | 'deinit' => 'function.destructor', 92 | 'func.op' => 'function.operator', 93 | 'type.method' => 'function.method.class', 94 | 'static.method' => 'function.method.static', 95 | 'method' => 'function.method.instance', 96 | 'func' => 'function.free', 97 | 'type.property' => 'var.class', 98 | 'static.property' => 'var.static', 99 | 'property' => 'var.instance', 100 | 'var' => 'var.global', 101 | 'subscript' => 'function.subscript', 102 | 'type.subscript' => 'function.subscript', 103 | 'static.subscript' => 'function.subscript', 104 | 'typealias' => 'typealias', 105 | 'associatedtype' => 'associatedtype', 106 | 'actor' => 'actor', 107 | 'macro' => 'macro', 108 | 'extension' => 'extension', 109 | }.freeze 110 | 111 | # We treat 'static var' differently to 'class var' 112 | # We treat actors as first-class entities 113 | def adjust_kind_for_declaration(kind, keywords) 114 | if kind == 'swift.class' && keywords.member?('actor') 115 | return 'swift.actor' 116 | end 117 | return kind unless keywords.member?('static') 118 | 119 | kind.gsub('type', 'static') 120 | end 121 | 122 | def init_kind(kind, keywords) 123 | adjusted = adjust_kind_for_declaration(kind, keywords) 124 | sourcekit_kind = KIND_MAP[adjusted.sub('swift.', '')] 125 | raise "Unknown symbol kind '#{kind}'" unless sourcekit_kind 126 | 127 | self.kind = "source.lang.swift.decl.#{sourcekit_kind}" 128 | end 129 | 130 | def extension? 131 | kind.end_with?('extension') 132 | end 133 | 134 | # Mapping SymbolGraph's ACL to SourceKit 135 | 136 | def init_acl(acl) 137 | self.acl = "source.lang.swift.accessibility.#{acl}" 138 | end 139 | 140 | # Symbol location - only available for public+ decls 141 | 142 | def init_location(loc_hash) 143 | self.location = {} 144 | location[:filename] = loc_hash[:uri].sub(%r{^file://}, '') 145 | location[:line] = loc_hash[:position][:line] 146 | location[:character] = loc_hash[:position][:character] 147 | end 148 | 149 | # Generic constraints: in one or both of two places. 150 | # There can be duplicates; these are removed by `Constraint`. 151 | def init_constraints(hash, raw_decl) 152 | raw_constraints = %i[swiftGenerics swiftExtension].flat_map do |key| 153 | next [] unless container = hash[key] 154 | 155 | container[:constraints] || [] 156 | end 157 | 158 | constraints = 159 | Constraint.new_list_for_symbol(raw_constraints, path_components) 160 | if raw_decl =~ / where (.*)$/ 161 | constraints += 162 | Constraint.new_list_from_declaration(Regexp.last_match[1]) 163 | end 164 | 165 | self.constraints = constraints.sort.uniq 166 | end 167 | 168 | # Generic type params 169 | def init_generic_type_params(hash) 170 | self.generic_type_params = Set.new( 171 | if (generics = hash[:swiftGenerics]) && 172 | (parameters = generics[:parameters]) 173 | parameters.map { |p| p[:name] } 174 | else 175 | [] 176 | end, 177 | ) 178 | end 179 | 180 | def init_doc_comments(comments_hash) 181 | self.doc_comments = comments_hash[:lines] 182 | .map { |l| l[:text] } 183 | .join("\n") 184 | end 185 | 186 | # Availability 187 | # Re-encode this as Swift. Should really teach Jazzy about these, 188 | # could maybe then do something smarter here. 189 | def availability_attributes(avail_hash_list) 190 | avail_hash_list.map do |avail| 191 | str = '@available(' 192 | if avail[:isUnconditionallyDeprecated] 193 | str += '*, deprecated' 194 | elsif domain = avail[:domain] 195 | str += domain 196 | %i[introduced deprecated obsoleted].each do |event| 197 | if version = avail[event] 198 | str += ", #{event}: #{decode_version(version)}" 199 | end 200 | end 201 | else 202 | warn "Found confusing availability: #{avail}" 203 | next nil 204 | end 205 | 206 | str += ", message: \"#{avail[:message]}\"" if avail[:message] 207 | str += ", renamed: \"#{avail[:renamed]}\"" if avail[:renamed] 208 | 209 | str + ')' 210 | end.compact 211 | end 212 | 213 | def decode_version(hash) 214 | str = hash[:major].to_s 215 | str += ".#{hash[:minor]}" if hash[:minor] 216 | str += ".#{hash[:patch]}" if hash[:patch] 217 | str 218 | end 219 | 220 | def spi_attributes 221 | spi ? ['@_spi(Unknown)'] : [] 222 | end 223 | 224 | def init_attributes(avail_hash_list) 225 | self.attributes = 226 | availability_attributes(avail_hash_list) + spi_attributes 227 | end 228 | 229 | # SourceKit common fields, shared by extension and regular symbols. 230 | # Things we do not know for fabricated extensions. 231 | def add_to_sourcekit(hash) 232 | unless doc_comments.nil? 233 | hash['key.doc.comment'] = doc_comments 234 | hash['key.doc.full_as_xml'] = '' 235 | end 236 | 237 | hash['key.accessibility'] = acl 238 | 239 | unless location.nil? 240 | hash['key.filepath'] = location[:filename] 241 | hash['key.doc.line'] = location[:line] + 1 242 | hash['key.doc.column'] = location[:character] + 1 243 | end 244 | 245 | hash 246 | end 247 | 248 | # Sort order 249 | include Comparable 250 | 251 | def <=>(other) 252 | # Things with location: order by file/line/column 253 | # (pls tell what wheel i am reinventing :/) 254 | if location && other_loc = other.location 255 | if location[:filename] == other_loc[:filename] 256 | if location[:line] == other_loc[:line] 257 | return location[:character] <=> other_loc[:character] 258 | end 259 | 260 | return location[:line] <=> other_loc[:line] 261 | end 262 | return location[:filename] <=> other_loc[:filename] 263 | end 264 | 265 | # Things with a location before things without a location 266 | return +1 if location.nil? && other.location 267 | return -1 if location && other.location.nil? 268 | 269 | # Things without a location: by name and then USR 270 | return usr <=> other.usr if name == other.name 271 | 272 | name <=> other.name 273 | end 274 | end 275 | end 276 | end 277 | # rubocop:enable Metrics/ClassLength 278 | -------------------------------------------------------------------------------- /lib/jazzy/jazzy_markdown.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'redcarpet' 4 | require 'rouge' 5 | require 'rouge/plugins/redcarpet' 6 | 7 | module Jazzy 8 | module Markdown 9 | # Publish if generated HTML needs math support 10 | class << self; attr_accessor :has_math; end 11 | 12 | module Footnotes 13 | # Global unique footnote ID 14 | def self.next_footnote 15 | @next_footnote ||= 0 16 | @next_footnote += 1 17 | end 18 | 19 | # Per-render map from user to global ID 20 | attr_accessor :footnotes_hash 21 | 22 | def reset 23 | @footnotes_hash = {} 24 | end 25 | 26 | def map_footnote(user_num) 27 | footnotes_hash.fetch(user_num) do 28 | footnotes_hash[user_num] = Footnotes.next_footnote 29 | end 30 | end 31 | 32 | def footnote_ref(num) 33 | mapped = map_footnote(num) 34 | "" \ 35 | "#{num}" 36 | end 37 | 38 | # follow native redcarpet: backlink goes before the first

tag 39 | def footnote_def(text, num) 40 | mapped = map_footnote(num) 41 | "\n
  • " + 42 | text.sub(%r{(?=

    )}, 43 | " ") + 44 | '
  • ' 45 | end 46 | end 47 | 48 | # rubocop:disable Metrics/ClassLength 49 | class JazzyHTML < Redcarpet::Render::HTML 50 | include Redcarpet::Render::SmartyPants 51 | include Rouge::Plugins::Redcarpet 52 | include Footnotes 53 | 54 | attr_accessor :default_language 55 | 56 | def header(text, header_level) 57 | text_slug = text.gsub(/[^[[:word:]]]+/, '-') 58 | .downcase 59 | .sub(/^-/, '') 60 | .sub(/-$/, '') 61 | 62 | "" \ 63 | "#{text}" \ 64 | "\n" 65 | end 66 | 67 | def codespan(text) 68 | case text 69 | when /^\$\$(.*)\$\$$/m 70 | o = ["

    ", 71 | Regexp.last_match[1], 72 | '

    '] 73 | Markdown.has_math = true 74 | when /^\$(.*)\$$/m 75 | o = ["", Regexp.last_match[1], ''] 76 | Markdown.has_math = true 77 | else 78 | o = ['', text.to_s, ''] 79 | end 80 | 81 | o[0] + CGI.escapeHTML(o[1]) + o[2] 82 | end 83 | 84 | # List from 85 | # https://github.com/apple/swift/blob/master/include/swift/Markup/SimpleFields.def 86 | UNIQUELY_HANDLED_CALLOUTS = %w[parameters 87 | parameter 88 | returns].freeze 89 | GENERAL_CALLOUTS = %w[attention 90 | author 91 | authors 92 | bug 93 | complexity 94 | copyright 95 | date 96 | experiment 97 | important 98 | invariant 99 | keyword 100 | mutatingvariant 101 | nonmutatingvariant 102 | note 103 | postcondition 104 | precondition 105 | recommended 106 | recommendedover 107 | remark 108 | remarks 109 | requires 110 | see 111 | seealso 112 | since 113 | todo 114 | throws 115 | version 116 | warning].freeze 117 | SPECIAL_LIST_TYPES = (UNIQUELY_HANDLED_CALLOUTS + GENERAL_CALLOUTS).freeze 118 | 119 | SPECIAL_LIST_TYPE_REGEX = %r{ 120 | \A\s* # optional leading spaces 121 | (

    \s*)? # optional opening p tag 122 | # any one of our special list types 123 | (#{SPECIAL_LIST_TYPES.map(&Regexp.method(:escape)).join('|')}) 124 | [\s:] # followed by either a space or a colon 125 | }ix.freeze 126 | 127 | ELIDED_LI_TOKEN = 128 | '7wNVzLB0OYPL2eGlPKu8q4vITltqh0Y6DPZf659TPMAeYh49o' 129 | 130 | def list_item(text, _list_type) 131 | if text =~ SPECIAL_LIST_TYPE_REGEX 132 | type = Regexp.last_match(2) 133 | if UNIQUELY_HANDLED_CALLOUTS.include? type.downcase 134 | return ELIDED_LI_TOKEN 135 | end 136 | 137 | return render_list_aside(type, 138 | text.sub(/#{Regexp.escape(type)}:\s+/, '')) 139 | end 140 | "

  • #{text.strip}
  • \n" 141 | end 142 | 143 | def render_list_aside(type, text) 144 | "#{render_aside(type, text).chomp}
      \n" 145 | end 146 | 147 | def render_aside(type, text) 148 | <<-HTML 149 |
      150 |

      #{type.underscore.humanize}

      151 | #{text} 152 |
      153 | HTML 154 | end 155 | 156 | def list(text, list_type) 157 | elided = text.gsub!(ELIDED_LI_TOKEN, '') 158 | return if text =~ /\A\s*\Z/ && elided 159 | 160 | tag = list_type == :ordered ? 'ol' : 'ul' 161 | "\n<#{tag}>\n#{text}\n" 162 | .gsub(%r{\n?
        \n?
      }, '') 163 | end 164 | 165 | # List from 166 | # https://developer.apple.com/documentation/xcode/formatting-your-documentation-content#Add-Notes-and-Other-Asides 167 | DOCC_CALLOUTS = %w[note 168 | important 169 | warning 170 | tip 171 | experiment].freeze 172 | 173 | DOCC_CALLOUT_REGEX = %r{ 174 | \A\s* # optional leading spaces 175 | (?:

      \s*)? # optional opening p tag 176 | # any one of the callout names 177 | (#{DOCC_CALLOUTS.map(&Regexp.method(:escape)).join('|')}) 178 | : # followed directly by a colon 179 | }ix.freeze 180 | 181 | def block_quote(html) 182 | if html =~ DOCC_CALLOUT_REGEX 183 | type = Regexp.last_match[1] 184 | render_aside(type, html.sub(/#{Regexp.escape(type)}:\s*/, '')) 185 | else 186 | "\n

      \n#{html}
      \n" 187 | end 188 | end 189 | 190 | def block_code(code, language) 191 | super(code, language || default_language) 192 | end 193 | 194 | def rouge_formatter(lexer) 195 | Highlighter::Formatter.new(lexer.tag) 196 | end 197 | end 198 | # rubocop:enable Metrics/ClassLength 199 | 200 | REDCARPET_OPTIONS = { 201 | autolink: true, 202 | fenced_code_blocks: true, 203 | no_intra_emphasis: true, 204 | strikethrough: true, 205 | space_after_headers: false, 206 | tables: true, 207 | lax_spacing: true, 208 | footnotes: true, 209 | }.freeze 210 | 211 | # Spot and capture returns & param HTML for separate display. 212 | class JazzyDeclarationHTML < JazzyHTML 213 | attr_reader :returns, :parameters 214 | 215 | def reset 216 | @returns = nil 217 | @parameters = {} 218 | super 219 | end 220 | 221 | INTRO_PAT = '\A(?\s*(

      \s*)?)' 222 | OUTRO_PAT = '(?.*)\z' 223 | 224 | RETURNS_REGEX = /#{INTRO_PAT}returns:#{OUTRO_PAT}/im.freeze 225 | 226 | IDENT_PAT = '(?\S+)' 227 | 228 | # Param formats: normal swift, objc via sourcekitten, and 229 | # possibly inside 'Parameters:' 230 | PARAM_PAT1 = "(parameter +#{IDENT_PAT}\\s*:)" 231 | PARAM_PAT2 = "(parameter:\\s*#{IDENT_PAT}\\s+)" 232 | PARAM_PAT3 = "(#{IDENT_PAT}\\s*:)" 233 | 234 | PARAM_PAT = "(?:#{PARAM_PAT1}|#{PARAM_PAT2}|#{PARAM_PAT3})" 235 | 236 | PARAM_REGEX = /#{INTRO_PAT}#{PARAM_PAT}#{OUTRO_PAT}/im.freeze 237 | 238 | def list_item(text, _list_type) 239 | if text =~ RETURNS_REGEX 240 | @returns = render_param_returns(Regexp.last_match) 241 | elsif text =~ PARAM_REGEX 242 | @parameters[Regexp.last_match(:param)] = 243 | render_param_returns(Regexp.last_match) 244 | end 245 | super 246 | end 247 | 248 | def render_param_returns(matches) 249 | body = matches[:intro].strip + matches[:outro].strip 250 | body = "

      #{body}

      " unless body.start_with?('

      ') 251 | # call smartypants for pretty quotes etc. 252 | postprocess(body) 253 | end 254 | end 255 | 256 | def self.renderer 257 | @renderer ||= JazzyDeclarationHTML.new 258 | end 259 | 260 | def self.markdown 261 | @markdown ||= Redcarpet::Markdown.new(renderer, REDCARPET_OPTIONS) 262 | end 263 | 264 | # Produces

      -delimited block content 265 | def self.render(markdown_text, default_language = nil) 266 | renderer.reset 267 | renderer.default_language = default_language 268 | markdown.render(markdown_text) 269 | end 270 | 271 | # Produces -delimited inline content 272 | def self.render_inline(markdown_text, default_language = nil) 273 | render(markdown_text, default_language) 274 | .sub(%r{^

      (.*)

      $}, '\1') 275 | end 276 | 277 | def self.rendered_returns 278 | renderer.returns 279 | end 280 | 281 | def self.rendered_parameters 282 | renderer.parameters 283 | end 284 | 285 | class JazzyCopyright < Redcarpet::Render::HTML 286 | def link(link, _title, content) 287 | %(#{content}) 289 | end 290 | end 291 | 292 | def self.copyright_markdown 293 | @copyright_markdown ||= Redcarpet::Markdown.new( 294 | JazzyCopyright, 295 | REDCARPET_OPTIONS, 296 | ) 297 | end 298 | 299 | def self.render_copyright(markdown_text) 300 | copyright_markdown.render(markdown_text) 301 | end 302 | end 303 | end 304 | --------------------------------------------------------------------------------