You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Hello friends, I'm not sure if my question fits better here or in the RuboCop project itself. Let's start here:
I'm building some smart cops that are crossing metadata from different perspectives of the application.
Initially, I build a mechanism to capture data from code and then I reuse it into cops to make them smart, understanding the context.
So, let's start with the capture system:
moduleRuboCop# RuboCop overrides for capturing information from files using NodePattern.moduleCapturemodule_function# Search for a node pattern on a given set of files# @return [Array<Object>] with results that matches or captures# @param files [Array<String>] nil for use## @example "Extract all class names from app/models folder"# RuboCop::Capture.all(# pattern: '(class $_ ...)',# files: Dir.glob('app/models/**/*.rb')# ).flatten.map(&:source) # ["ModelA", "ModelB", ...]## Or use from:## @example "Extract all class names from app/models folder"# RuboCop::Capture.all(# pattern: '(class $_ ...)',# from: 'app/models/*.rb'# ).flatten.map(&:source) # ["ModelA", "ModelB",...]# Or use from:defall(pattern:,files: nil,from: nil)node_pattern=RuboCop::NodePattern.new(pattern)files=files_from(from)iffrom && files.nil?files.mapdo |file|
FromFile.new(pattern: node_pattern,file: file).searchendend# @return [Array<String>] with all files from recursive search.# @example "Factories from all engines"# RuboCop::Capture.files_from('engines/*/spec/factories')# @see https://apidock.com/ruby/Dir/glob/classdeffiles_from(dir)Dir.glob("#{dir}/**/**.rb")end# This class searches and captures# node or data from AST using [RuboCop::NodePattern].# @example "Extract all class names from app/models folder"# RuboCop::Capture::FromFile.all(# pattern: '(class $_ ...)',# files: Dir.glob('app/models/*.rb')# ).flatten.map(&:source) # ["ModelA", "ModelB", ...]## @example "Extracting class name from single file"# RuboCop::Capture::FromFile.new(# pattern: '(class $_ ...)',# file: 'app/models/user.rb'# ).search => s(:const, nil, :User)classFromFileincludeRuboCop::AST::Traversalattr_reader:pattern,:filedefinitialize(pattern:,file:)@pattern=pattern.is_a?(String) ? RuboCop::NodePattern.new(pattern) : pattern@file=fileend# @return [Array<Object>] captures from node# @param pattern String with the node pattern# @param node [RuboCop::AST::Node]defsearch(pattern=self.pattern,node=ast_from_file)match=pattern.match(node)ifmatchmatch == true ? node : matchelsifnode.nil?nilelsifnode.children.nil?[]elsifnode.descendants.any?node.descendants.flat_map{ |e| search(pattern,e)}.flatten.compactendend# @return [RuboCop::AST::Node] from #processed_sourcedefast_from_fileprocessed_source.astend# @return [RuboCop::ProcessedSource] from #file using RUBY_VERSIONdefprocessed_sourceRuboCop::ProcessedSource.new(IO.read(file),RUBY_VERSION.to_f,file)endendendend
And then I can start capturing things. Example, Let's say I'd like to capture all factory names from an specific folder:
# @return [Array<String>] with the definition of factory class names' from a# given folder.deffactory_names(from:)(@factories_names ||= {})[from] ||=
RuboCop::Capture.all(pattern: '(send nil? :factory ({sym str} $_) ... )',from: from).flatten.compact.uniqend
Then I can build a cop to check if some factories from rails engines are used outside of the boundaries of the domain. Ideally, a rails engine should have the domain isolated and do not know about the factories of other engines or the main rails application. The inverse is also valid, so I have two cops to check if the main app is touching engine code and another to check if the engine code is using the main app code.
classAvoidRailsModelsOnEngines < CopMSG='Do not use models from rails inside the engines.'.freezedef_node_matcher:rails_model?,<<~PATTERN (send (const {nil? cbase} #forbidden_model?) ...) PATTERNclass << self# @return [Array<String>] with all class names from modelsdefrails_modelscapture_const_name(from: model_files)end# @return [Array<String>] with all filenames from models ignoring concernsdefmodel_filesDir.glob('app/models/**/**.rb').reject{ |e| e =~ %r{/concerns/}}end# @return [Array<String>] with the definition of class names' from a# given folder.defcapture_const_name(from:)(@consts_from ||= {})[from] ||=
beginresults=RuboCop::Capture.all(pattern: '(class $_ ...)',files: from)results.flatten.compact.flat_map(&:source).uniqendendenddefon_send(node)returnunlessrails_model?(node)add_offense(node.children.first)endprivatedefforbidden_model?(name)models.include?(name.to_s)enddefmodels(self.class.rails_models - engine_models)enddefengine_modelsself.class.capture_const_namefrom: files_from_current_engineenddeffiles_from_current_engineall_files_from_engine_folder.reject{ |e| e =~ %r{/concerns/}}enddefall_files_from_engine_folderdir=File.dirname(processed_source.path)folders=dir.split('/')engines_folder_index=folders.index('engines')return[]unlessengines_folder_indexfolder_engine=folders[0,(engines_folder_index + 2)]Dir.glob(File.join(*folder_engine,'**','**.rb'))endend
When I run RuboCop sequentially it works like a charm, caching the @consts_from, but if I need to introduce a parallel process it becomes heavy and slow down blocking all threads until captures all cases in all threads separately.
I'm not very familiar with the caching system and I'm not sure how can I build such captures in a way that it would process only once. I don't know exactly what is the best way to warm up this cache in a way that we don't lock all cops to work.
For now, my solution was just to isolate the cops into a separated .rubocop.yml configuration and just run them isolated disabling all other cops, but I'd love to hear any tips on how to better enhance the capturing system into the RuboCop ecosystem in a way that it would not hurt to run along with all other cops.
The text was updated successfully, but these errors were encountered:
My summary is: you want to write a Cop that checks a set of files my_engine/* using information gathered from a (distinct?) set of files my_app/*, right?
There's clearly not an easy way to do this right now. This feels related to #7968 but goes beyond that. It feels outside the scope of RuboCop tbh, but could make for a nice gem?
My summary is: you want to write a Cop that checks a set of files my_engine/* using information gathered from a (distinct?) set of files my_app/*, right?
Hello friends, I'm not sure if my question fits better here or in the RuboCop project itself. Let's start here:
I'm building some smart cops that are crossing metadata from different perspectives of the application.
Initially, I build a mechanism to capture data from code and then I reuse it into cops to make them smart, understanding the context.
So, let's start with the capture system:
And then I can start capturing things. Example, Let's say I'd like to capture all factory names from an specific folder:
Then I can build a cop to check if some factories from rails engines are used outside of the boundaries of the domain. Ideally, a rails engine should have the domain isolated and do not know about the factories of other engines or the main rails application. The inverse is also valid, so I have two cops to check if the main app is touching engine code and another to check if the engine code is using the main app code.
When I run RuboCop sequentially it works like a charm, caching the
@consts_from
, but if I need to introduce a parallel process it becomes heavy and slow down blocking all threads until captures all cases in all threads separately.I'm not very familiar with the caching system and I'm not sure how can I build such captures in a way that it would process only once. I don't know exactly what is the best way to warm up this cache in a way that we don't lock all cops to work.
For now, my solution was just to isolate the cops into a separated
.rubocop.yml
configuration and just run them isolated disabling all other cops, but I'd love to hear any tips on how to better enhance the capturing system into the RuboCop ecosystem in a way that it would not hurt to run along with all other cops.The text was updated successfully, but these errors were encountered: