# frozen_string_literal: true# Class for generating documentation of all cops departments# @api privateclassCopsDocumentationGenerator# rubocop:disable Metrics/ClassLengthinclude::RuboCop::Cop::Documentation# This class will only generate documentation for cops that belong to one of# the departments given in the `departments` array. E.g. if we only wanted# documentation for Lint cops:## CopsDocumentationGenerator.new(departments: ['Lint']).call#definitialize(departments: [])@departments=departments.map(&:to_sym).sort!@cops=RuboCop::Cop::Registry.global@config=RuboCop::ConfigLoader.default_configurationenddefcallYARD::Registry.load!departments.each{|department|print_cops_of_department(department)}print_table_of_contentsensureRuboCop::ConfigLoader.default_configuration=nilendprivateattr_reader:departments,:cops,:configdefcops_of_department(department)cops.with_department(department).sort!enddefcops_body(cop,description,examples_objects,pars)content=h2(cop.cop_name)content<<required_ruby_version(cop)content<<properties(cop)content<<"#{description}\n"content<<examples(examples_objects)ifexamples_objects.count.positive?content<<configurations(pars)content<<references(cop)contentenddefexamples(examples_object)examples_object.each_with_object(h3('Examples').dup)do|example,content|content<<"\n"unlesscontent.end_with?("\n\n")content<<h4(example.name)unlessexample.name==''content<<code_example(example)endenddefrequired_ruby_version(cop)return''unlesscop.respond_to?(:required_minimum_ruby_version)"NOTE: Required Ruby version: #{cop.required_minimum_ruby_version}\n\n"end# rubocop:disable Metrics/MethodLengthdefproperties(cop)header=['Enabled by default','Safe','Supports autocorrection','VersionAdded','VersionChanged']autocorrect=ifcop.support_autocorrect?"Yes#{' (Unsafe)'unlesscop.new(config).safe_autocorrect?}"else'No'endcop_config=config.for_cop(cop)content=[[cop_status(cop_config.fetch('Enabled')),cop_config.fetch('Safe',true)?'Yes':'No',autocorrect,cop_config.fetch('VersionAdded','-'),cop_config.fetch('VersionChanged','-')]]"#{to_table(header,content)}\n"end# rubocop:enable Metrics/MethodLengthdefh2(title)content=+"\n"content<<"== #{title}\n"content<<"\n"contentenddefh3(title)content=+"\n"content<<"=== #{title}\n"content<<"\n"contentenddefh4(title)content=+"==== #{title}\n"content<<"\n"contentenddefcode_example(ruby_code)content=+"[source,ruby]\n----\n"content<<ruby_code.text.gsub('@good','# good').gsub('@bad','# bad').stripcontent<<"\n----\n"contentenddefconfigurations(pars)return''ifpars.empty?header=['Name','Default value','Configurable values']configs=pars.each_key.reject{|key|key.start_with?('Supported')}.reject{|key|key.start_with?('AllowMultipleStyles')}content=configs.mapdo|name|configurable=configurable_values(pars,name)default=format_table_value(pars[name])[name,default,configurable]endh3('Configurable attributes')+to_table(header,content)end# rubocop:disable Metrics/CyclomaticComplexity,Metrics/MethodLengthdefconfigurable_values(pars,name)casenamewhen/^Enforced/supported_style_name=RuboCop::Cop::Util.to_supported_styles(name)format_table_value(pars[supported_style_name])when'IndentationWidth''Integer'when'Database'format_table_value(pars['SupportedDatabases'])elsecasepars[name]whenString'String'whenInteger'Integer'whenFloat'Float'whentrue,false'Boolean'whenArray'Array'else''endendend# rubocop:enable Metrics/CyclomaticComplexity,Metrics/MethodLengthdefto_table(header,content)table=['|===',"| #{header.join(' | ')}\n\n"].join("\n")marked_contents=content.mapdo|plain_content|plain_content.map{|c|"| #{c}"}.join("\n")endtable<<marked_contents.join("\n\n")table<<"\n|===\n"enddefformat_table_value(val)value=casevalwhenArrayifval.empty?'`[]`'elseval.map{|config|format_table_value(config)}.join(', ')endelsewrap_backtick(val.nil??'<none>':val)endvalue.gsub("#{Dir.pwd}/",'').rstripenddefwrap_backtick(value)ifvalue.is_a?(String)# Use `+` to prevent text like `**/*.gemspec` from being bold.value.start_with?('*')?"`+#{value}+`":"`#{value}`"else"`#{value}`"endenddefreferences(cop)cop_config=config.for_cop(cop)urls=RuboCop::Cop::MessageAnnotator.new(config,cop.name,cop_config,{}).urlsreturn''ifurls.empty?content=h3('References')content<<urls.map{|url|"* #{url}"}.join("\n")content<<"\n"contentenddefprint_cops_of_department(department)selected_cops=cops_of_department(department)content=+"= #{department}\n"selected_cops.each{|cop|content<<print_cop_with_doc(cop)}file_name="#{Dir.pwd}/docs/modules/ROOT/pages/#{department_to_basename(department)}.adoc"File.open(file_name,'w')do|file|puts"* generated #{file_name}"file.write("#{content.strip}\n")endenddefprint_cop_with_doc(cop)cop_config=config.for_cop(cop)non_display_keys=%w[
Description Enabled StyleGuide Reference Safe SafeAutoCorrect VersionAdded
VersionChanged
]pars=cop_config.reject{|k|non_display_keys.include?k}description='No documentation'examples_object=[]cop_code(cop)do|code_object|description=code_object.docstringunlesscode_object.docstring.blank?examples_object=code_object.tags('example')endcops_body(cop,description,examples_object,pars)enddefcop_code(cop)YARD::Registry.all(:class).detectdo|code_object|nextunlessRuboCop::Cop::Badge.for(code_object.to_s)==cop.badgeyieldcode_objectendenddeftable_of_content_for_department(department)type_title=department[0].upcase+department[1..-1]filename="#{department_to_basename(department)}.adoc"content=+"=== Department xref:#{filename}[#{type_title}]\n\n"cops_of_department(department).eachdo|cop|anchor=cop.cop_name.sub('/','').downcasecontent<<"* xref:#{filename}##{anchor}[#{cop.cop_name}]\n"endcontentenddefprint_table_of_contentspath="#{Dir.pwd}/docs/modules/ROOT/pages/cops.adoc"original=File.read(path)content=+"// START_COP_LIST\n\n"content<<table_contentscontent<<"\n// END_COP_LIST"content=original.sub(%r{// START_COP_LIST.+// END_COP_LIST}m,content)File.write(path,content)enddeftable_contentsdepartments.map{|department|table_of_content_for_department(department)}.join("\n")enddefcop_status(status)return'Disabled'unlessstatusstatus=='pending'?'Pending':'Enabled'endend