Class: GraphQL::Modernize::Application

Inherits:
Object
  • Object
show all
Defined in:
lib/graphql/modernize/application.rb

Defined Under Namespace

Classes: FileResult, Result

Instance Method Summary collapse

Constructor Details

#initialize(config:, rules: RuleRegistry.all, jobs: Etc.nprocessors, explicit_rule_ids: []) ⇒ Application

Returns a new instance of Application.

Raises:



12
13
14
15
16
17
18
# File 'lib/graphql/modernize/application.rb', line 12

def initialize(config:, rules: RuleRegistry.all, jobs: Etc.nprocessors, explicit_rule_ids: [])
  @config = config
  @rules = rules
  @explicit_rule_ids = explicit_rule_ids.map { |id| id.to_s.upcase }.freeze
  @jobs = Integer(jobs)
  raise Error, "jobs must be an integer greater than or equal to 1" unless @jobs.positive?
end

Instance Method Details

#ensure_write_safe!(paths: [], force: false) ⇒ Object

Raises:



67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
# File 'lib/graphql/modernize/application.rb', line 67

def ensure_write_safe!(paths: [], force: false)
  return if force

  output, _error, status = Open3.capture3("git", "-C", @config.root, "rev-parse", "--is-inside-work-tree")
  unless status.success? && output.strip == "true"
    raise Error, "--write must run inside a Git working tree (use --force to override)"
  end

  output, error, status = Open3.capture3("git", "-C", @config.root, "status", "--porcelain")
  raise Error, "Unable to inspect the Git working tree: #{error.strip}" unless status.success?

  raise Error, "The working tree is not clean (use --force to override)" unless output.empty?

  output, error, status = Open3.capture3("git", "-C", @config.root, "rev-parse", "--show-toplevel")
  raise Error, "Unable to determine the Git working tree root: #{error.strip}" unless status.success?

  root = File.realpath(output.strip)
  prefix = "#{root}#{File::SEPARATOR}"
  files = FileFinder.new(@config).find(paths)
  unsafe = files.find do |path|
    !File.realpath(path).start_with?(prefix) || File.lstat(path).symlink?
  end
  raise Error, "A --write target is outside the Git working tree or is a symbolic link: #{unsafe}" if unsafe

  output, error, status = Open3.capture3("git", "-C", root, "ls-files", "-z")
  raise Error, "Unable to inspect Git-tracked files: #{error.strip}" unless status.success?

  tracked = output.split("\0").to_h { |path| [File.join(root, path), true] }
  untracked = files.find { |path| !tracked[File.realpath(path)] }
  raise Error, "A --write target is not tracked by Git: #{untracked}" if untracked
end

#run(paths = []) ⇒ Object



20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
# File 'lib/graphql/modernize/application.rb', line 20

def run(paths = [])
  files = FileFinder.new(@config).find(paths)
  originals = files.to_h do |path|
    source_file = Astel::SourceFile.parse(path: path, version: @config.ruby_version)
    [path, source_file.source]
  end
  current = originals.dup
  corrected_offenses = Hash.new { |hash, path| hash[path] = [] }
  current_offenses = Hash.new { |hash, path| hash[path] = [] }
  parse_errors = []
  iterations = 0

  (@config.iterations + 2).times do |iteration|
    iterations = iteration + 1
    source_files = parse_sources(current, parse_errors)
    hierarchy = ClassHierarchy.new(base_classes: @config.base_classes).build(source_files)
    iteration_results = process_files(source_files.map(&:path), current, hierarchy)
    changed = false

    iteration_results.each do |path, rewritten, found|
      current_offenses[path] = found
      corrected_offenses[path].concat(found.select(&:corrected?))
      changed ||= current[path] != rewritten
      current[path] = rewritten
    end
    break unless changed

    next unless iterations > @config.iterations + 1

    rule_ids = iteration_results.flat_map do |_path, _rewritten, found|
      found.filter_map do |offense|
        offense.rule_id if offense.corrected? || offense.conflicting_rule_id
      end
    end.uniq.sort
    detail = rule_ids.empty? ? "" : " (related rules: #{rule_ids.join(', ')})"
    raise Error, "rewrites did not converge after #{@config.iterations} retry iterations#{detail}"
  end

  results = originals.map do |path, source|
    source_file = Astel::SourceFile.from_string(source, path: path, version: @config.ruby_version)
    unresolved = current_offenses[path].reject(&:corrected?)
    FileResult.new(source_file: source_file, rewritten: current[path],
                   offenses: corrected_offenses[path] + unresolved)
  end
  Result.new(files: results, parse_errors: parse_errors.uniq, iterations: iterations)
end

#write(result) ⇒ Object



99
100
101
102
103
104
105
# File 'lib/graphql/modernize/application.rb', line 99

def write(result)
  result.files.each do |file|
    next if file.source_file.source == file.rewritten

    File.binwrite(file.source_file.path, file.rewritten)
  end
end