# frozen_string_literal: truemoduleRuboCop# This class parses the special `rubocop:disable` comments in a source# and provides a way to check if each cop is enabled at arbitrary line.classCommentConfigUNNEEDED_DISABLE='Lint/UnneededCopDisableDirective'.freezeCOP_NAME_PATTERN='([A-Z]\w+/)?(?:[A-Z]\w+)'.freezeCOP_NAMES_PATTERN="(?:#{COP_NAME_PATTERN} , )*#{COP_NAME_PATTERN}".freezeCOPS_PATTERN="(all|#{COP_NAMES_PATTERN})".freezeCOMMENT_DIRECTIVE_REGEXP=Regexp.new(('# rubocop : ((?:dis|en)able)\b '+COPS_PATTERN).gsub(' ','\s*'))CopAnalysis=Struct.new(:line_ranges,:start_line_number)attr_reader:processed_sourcedefinitialize(processed_source)@processed_source=processed_sourceenddefcop_enabled_at_line?(cop,line_number)cop=cop.cop_nameifcop.respond_to?(:cop_name)disabled_line_ranges=cop_disabled_line_ranges[cop]returntrueunlessdisabled_line_rangesdisabled_line_ranges.none?{|range|range.include?(line_number)}enddefcop_disabled_line_ranges@cop_disabled_line_ranges||=analyzeenddefextra_enabled_commentsextra_enabled_comments_with_names([],{})endprivatedefextra_enabled_comments_with_names(extras,names)each_directivedo|comment,cop_names,disabled|nextunlesscomment_only_line?(comment.loc.expression.line)cop_names.eachdo|name|names[name]||=0ifdisablednames[name]+=1elsifnames[name]>0names[name]-=1elseextras<<[comment,name]endendendextrasenddefanalyzeanalyses=Hash.new{|hash,key|hash[key]=CopAnalysis.new([],nil)}each_mentioned_copdo|cop_name,disabled,line,single_line|analyses[cop_name]=analyze_cop(analyses[cop_name],disabled,line,single_line)endanalyses.each_with_object({})do|element,hash|cop_name,analysis=*elementhash[cop_name]=cop_line_ranges(analysis)endenddefanalyze_cop(analysis,disabled,line,single_line)ifsingle_lineanalyze_single_line(analysis,line,disabled)elsifdisabledanalyze_disabled(analysis,line)elseanalyze_rest(analysis,line)endenddefanalyze_single_line(analysis,line,disabled)returnanalysisunlessdisabledCopAnalysis.new(analysis.line_ranges+[(line..line)],analysis.start_line_number)enddefanalyze_disabled(analysis,line)if(start_line=analysis.start_line_number)# Cop already disabled on this line, so we end the current disabled# range before we start a new range.returnCopAnalysis.new(analysis.line_ranges+[start_line..line],line)endCopAnalysis.new(analysis.line_ranges,line)enddefanalyze_rest(analysis,line)if(start_line=analysis.start_line_number)returnCopAnalysis.new(analysis.line_ranges+[start_line..line],nil)endCopAnalysis.new(analysis.line_ranges,nil)enddefcop_line_ranges(analysis)returnanalysis.line_rangesunlessanalysis.start_line_numberanalysis.line_ranges+[(analysis.start_line_number..Float::INFINITY)]enddefeach_mentioned_copeach_directivedo|comment,cop_names,disabled|comment_line_number=comment.loc.expression.linesingle_line=!comment_only_line?(comment_line_number)cop_names.eachdo|cop_name|yieldqualified_cop_name(cop_name),disabled,comment_line_number,single_lineendendenddefeach_directivereturnifprocessed_source.comments.nil?processed_source.each_commentdo|comment|directive=directive_parts(comment)nextunlessdirectiveyieldcomment,*directiveendenddefdirective_parts(comment)match=comment.text.match(COMMENT_DIRECTIVE_REGEXP)returnunlessmatchswitch,cops_string=match.capturescop_names=cops_string=='all'?all_cop_names:cops_string.split(/,\s*/)disabled=(switch=='disable')[cop_names,disabled]enddefqualified_cop_name(cop_name)Cop::Cop.qualified_cop_name(cop_name.strip,processed_source.file_path)enddefall_cop_names@all_cop_names||=Cop::Cop.registry.names-[UNNEEDED_DISABLE]enddefcomment_only_line?(line_number)non_comment_token_line_numbers.none?do|non_comment_line_number|non_comment_line_number==line_numberendenddefnon_comment_token_line_numbers@non_comment_token_line_numbers||=beginnon_comment_tokens=processed_source.tokens.reject(&:comment?)non_comment_tokens.map(&:line).uniqendendendend