Module: Fast
- Defined in:
- lib/fast.rb,
lib/fast/cli.rb,
lib/fast/version.rb,
lib/fast/experiment.rb
Overview
Allow to replace code managing multiple replacements and combining replacements. Useful for large codebase refactor and multiple replacements in the same file.
Defined Under Namespace
Classes: All, Any, Capture, Cli, Experiment, ExperimentCombinations, ExperimentFile, ExpressionParser, Find, FindFromArgument, FindString, FindWithCapture, InstanceMethodCall, Matcher, Maybe, MethodCall, Not, Parent, Rewriter
Constant Summary collapse
- LITERAL =
Literals are shortcuts allowed inside ExpressionParser
{ '...' => ->(node) { node&.children&.any? }, '_' => ->(node) { !node.nil? }, 'nil' => nil }.freeze
- TOKENIZER =
Allowed tokens in the node pattern domain
%r/ [\+\-\/\*\\!] # operators or negation | ===? # == or === | \d+\.\d* # decimals and floats | "[^"]+" # strings | _ # something not nil: match | \.{3} # a node with children: ... | \[|\] # square brackets `[` and `]` for all | \^ # node has children with | \? # maybe expression | [\d\w_]+[\\!\?]? # method names or numbers | \(|\) # parens `(` and `)` for tuples | \{|\} # curly brackets `{` and `}` for any | \$ # capture | \#\w[\d\w_]+[\\!\?]? # custom method call | \.\w[\d\w_]+\? # instance method call | \\\d # find using captured expression | %\d # bind extra arguments to the expression /x.freeze
- VERSION =
'0.1.0'
Class Attribute Summary collapse
-
.debugging ⇒ Object
Returns the value of attribute debugging.
-
.experiments ⇒ Object
readonly
Returns the value of attribute experiments.
Class Method Summary collapse
-
.ast(content, buffer_name: '(string)') ⇒ Astrolabe::Node
From the parsed content.
-
.ast_from_file(file) ⇒ Astrolabe::Node
caches the content based on the filename.
-
.capture(node, pattern) ⇒ Array<Object>
Return only captures from a search If the result is only a single capture, it will return the single element.
-
.capture_file(pattern, file) ⇒ Array<Object>
Capture elements from searches in files.
-
.debug ⇒ Object
Utility function to inspect search details using debug block.
-
.experiment(name, &block) ⇒ Object
Fast.experiment is a shortcut to define new experiments and allow them to work together in experiment combinations.
- .expression(string) ⇒ Object
-
.expression_from(node) ⇒ String
Extracts a node pattern expression from a given node supressing identifiers and primitive types.
-
.highlight(node, show_sexp: false) ⇒ Object
Highligh some source code based on the node.
-
.match?(ast, pattern, *args) ⇒ Boolean
Verify if a given AST matches with a specific pattern.
-
.replace(ast, pattern, &replacement) ⇒ String
Replaces content based on a pattern.
-
.replace_file(file, pattern, &replacement) ⇒ Object
Replaces the source of an Fast.ast_from_file with and the same source if the pattern does not match.
-
.report(result, show_sexp: nil, file: nil) ⇒ Object
Combines Fast.highlight with files printing file name in the head with the source line.
-
.ruby_files_from(*files) ⇒ Array<String>
When the argument is a folder, it recursively fetches all ‘.rb` files from it.
-
.search(node, pattern) { ... } ⇒ Object
Search recursively into a node and its children.
-
.search_file(pattern, file) ⇒ Array<Astrolabe::Node>
Search with pattern directly on file.
Class Attribute Details
.debugging ⇒ Object
Returns the value of attribute debugging.
203 204 205 |
# File 'lib/fast.rb', line 203 def debugging @debugging end |
.experiments ⇒ Object (readonly)
Returns the value of attribute experiments.
30 31 32 |
# File 'lib/fast/experiment.rb', line 30 def experiments @experiments end |
Class Method Details
.ast(content, buffer_name: '(string)') ⇒ Astrolabe::Node
Returns from the parsed content.
74 75 76 77 78 |
# File 'lib/fast.rb', line 74 def ast(content, buffer_name: '(string)') buffer = Parser::Source::Buffer.new(buffer_name) buffer.source = content Parser::CurrentRuby.new(Astrolabe::Builder.new).parse(buffer) end |
.ast_from_file(file) ⇒ Astrolabe::Node
caches the content based on the filename.
84 85 86 87 |
# File 'lib/fast.rb', line 84 def ast_from_file(file) @cache ||= {} @cache[file] ||= ast(IO.read(file), buffer_name: file) end |
.capture(node, pattern) ⇒ Array<Object>
Return only captures from a search If the result is only a single capture, it will return the single element.
160 161 162 163 164 165 166 167 168 169 170 |
# File 'lib/fast.rb', line 160 def capture(node, pattern) res = if (match = Fast.match?(node, pattern)) match == true ? node : match elsif node.child_nodes.any? node.each_child_node .flat_map { |child| capture(child, pattern) } .compact.flatten end res&.size == 1 ? res[0] : res end |
.capture_file(pattern, file) ⇒ Array<Object>
Capture elements from searches in files. Keep in mind you need to use ‘$` in the pattern to make it work.
137 138 139 140 |
# File 'lib/fast.rb', line 137 def capture_file(pattern, file) node = ast_from_file(file) capture node, pattern end |
.debug ⇒ Object
Utility function to inspect search details using debug block.
It prints output of all matching cases.
int == (int 1) # => true
1 == 1 # => true
215 216 217 218 219 220 221 222 223 224 225 226 227 228 |
# File 'lib/fast.rb', line 215 def debug return yield if debugging self.debugging = true result = nil Find.class_eval do alias_method :original_match_recursive, :match_recursive alias_method :match_recursive, :debug_match_recursive result = yield alias_method :match_recursive, :original_match_recursive # rubocop:disable Lint/DuplicateMethods end self.debugging = false result end |
.experiment(name, &block) ⇒ Object
Fast.experiment is a shortcut to define new experiments and allow them to work together in experiment combinations.
The following experiment look into ‘spec` folder and try to remove `before` and `after` blocks on testing code. Sometimes they’re not effective and we can avoid the hard work of do it manually.
If the spec does not fail, it keeps the change.
25 26 27 28 |
# File 'lib/fast/experiment.rb', line 25 def experiment(name, &block) @experiments ||= {} @experiments[name] = Experiment.new(name, &block) end |
.expression(string) ⇒ Object
199 200 201 |
# File 'lib/fast.rb', line 199 def expression(string) ExpressionParser.new(string).parse end |
.expression_from(node) ⇒ String
Extracts a node pattern expression from a given node supressing identifiers and primitive types. Useful to index abstract patterns or similar code structure.
253 254 255 256 257 258 259 260 261 262 263 264 265 |
# File 'lib/fast.rb', line 253 def expression_from(node) case node when Parser::AST::Node children_expression = node.children.map(&Fast.method(:expression_from)).join(' ') "(#{node.type}#{' ' + children_expression if node.children.any?})" when nil, 'nil' 'nil' when Symbol, String, Numeric '_' when Array, Hash '...' end end |
.highlight(node, show_sexp: false) ⇒ Object
Highligh some source code based on the node. Useful for printing code with syntax highlight.
174 175 176 177 178 179 180 181 182 |
# File 'lib/fast.rb', line 174 def highlight(node, show_sexp: false) output = if node.respond_to?(:loc) && !show_sexp node.loc.expression.source else node end CodeRay.scan(output, :ruby).term end |
.match?(ast, pattern, *args) ⇒ Boolean
Verify if a given AST matches with a specific pattern
93 94 95 |
# File 'lib/fast.rb', line 93 def match?(ast, pattern, *args) Matcher.new(ast, pattern, *args).match? end |
.replace(ast, pattern, &replacement) ⇒ String
Replaces content based on a pattern.
107 108 109 110 111 112 113 114 115 116 117 118 |
# File 'lib/fast.rb', line 107 def replace(ast, pattern, &replacement) buffer = Parser::Source::Buffer.new('replacement') buffer.source = ast.loc.expression.source to_replace = search(ast, pattern) types = to_replace.grep(Parser::AST::Node).map(&:type).uniq rewriter = Rewriter.new rewriter.buffer = buffer rewriter.search = pattern rewriter.replacement = replacement rewriter.affect_types(*types) rewriter.rewrite(buffer, ast) end |
.replace_file(file, pattern, &replacement) ⇒ Object
Replaces the source of an ast_from_file with and the same source if the pattern does not match.
122 123 124 125 |
# File 'lib/fast.rb', line 122 def replace_file(file, pattern, &replacement) ast = ast_from_file(file) replace(ast, pattern, &replacement) end |
.report(result, show_sexp: nil, file: nil) ⇒ Object
Combines highlight with files printing file name in the head with the source line.
191 192 193 194 195 196 197 |
# File 'lib/fast.rb', line 191 def report(result, show_sexp: nil, file: nil) if file line = result.loc.expression.line if result.is_a?(Parser::AST::Node) puts Fast.highlight("# #{file}:#{line}") end puts Fast.highlight(result, show_sexp: show_sexp) end |
.ruby_files_from(*files) ⇒ Array<String>
When the argument is a folder, it recursively fetches all ‘.rb` files from it.
233 234 235 236 237 238 239 240 241 242 |
# File 'lib/fast.rb', line 233 def ruby_files_from(*files) directories = files.select(&File.method(:directory?)) if directories.any? files -= directories files |= directories.flat_map { |dir| Dir["#{dir}/**/*.rb"] } files.uniq! end files end |
.search(node, pattern) { ... } ⇒ Object
Search recursively into a node and its children. If the node matches with the pattern it returns the node, otherwise it recursively collect possible children nodes
146 147 148 149 150 151 152 153 154 155 |
# File 'lib/fast.rb', line 146 def search(node, pattern) if (match = Fast.match?(node, pattern)) yield node, match if block_given? match != true ? [node, match] : [node] elsif Fast.match?(node, '...') node.each_child_node .flat_map { |e| search(e, pattern) } .compact.flatten end end |
.search_file(pattern, file) ⇒ Array<Astrolabe::Node>
Search with pattern directly on file
129 130 131 132 |
# File 'lib/fast.rb', line 129 def search_file(pattern, file) node = ast_from_file(file) search node, pattern end |