autoload-checker 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 6e5e30b050ff00a10be2fbb62a51bb5b57f7a7cfe7f612fd7afdf2a99e2c4028
4
+ data.tar.gz: 1f16534a3b8a2965c4e08b1d1e35bab5fa43d0b421cd697d013fa761888183c3
5
+ SHA512:
6
+ metadata.gz: fc1269943f368984f3c3bebe7ab836a61bd57b4e985409b76f6443c0644097da00df194a96592951675a0decf1e438fb7a73034c25b9b3d7e2f668a0dd3d98d8
7
+ data.tar.gz: a611efd324d67b824680ceb70aead2410574ca03e51256e19e9dc668d0b185cbf26603682333c473c66b8683639207c16f2752920c867a97629089fd63f09a3e
data/Gemfile ADDED
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem "rspec", "~> 3.11", :group => :test
data/Gemfile.lock ADDED
@@ -0,0 +1,26 @@
1
+ GEM
2
+ remote: https://rubygems.org/
3
+ specs:
4
+ diff-lcs (1.5.0)
5
+ rspec (3.11.0)
6
+ rspec-core (~> 3.11.0)
7
+ rspec-expectations (~> 3.11.0)
8
+ rspec-mocks (~> 3.11.0)
9
+ rspec-core (3.11.0)
10
+ rspec-support (~> 3.11.0)
11
+ rspec-expectations (3.11.0)
12
+ diff-lcs (>= 1.2.0, < 2.0)
13
+ rspec-support (~> 3.11.0)
14
+ rspec-mocks (3.11.1)
15
+ diff-lcs (>= 1.2.0, < 2.0)
16
+ rspec-support (~> 3.11.0)
17
+ rspec-support (3.11.0)
18
+
19
+ PLATFORMS
20
+ x86_64-darwin-21
21
+
22
+ DEPENDENCIES
23
+ rspec (~> 3.11)
24
+
25
+ BUNDLED WITH
26
+ 2.3.11
data/LICENSE ADDED
@@ -0,0 +1,13 @@
1
+ DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
2
+ Version 2, December 2004
3
+
4
+ Copyright (C) 2022 cyberfined
5
+
6
+ Everyone is permitted to copy and distribute verbatim or modified
7
+ copies of this license document, and changing it is allowed as long
8
+ as the name is changed.
9
+
10
+ DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
11
+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
12
+
13
+ 0. You just DO WHAT THE FUCK YOU WANT TO.
data/README.md ADDED
@@ -0,0 +1,35 @@
1
+ # autoload_checker
2
+
3
+ Checks for conflicts in class/module definitions and corrects them. For instance, you have
4
+ a file `foo/bar/baz.rb` with following content:
5
+
6
+ ```ruby
7
+ module Foo
8
+ module Bar
9
+ class Baz
10
+ end
11
+ end
12
+ end
13
+ ```
14
+
15
+ And a file `foo/bar.rb` with following content:
16
+
17
+ ```ruby
18
+ module Foo
19
+ class Bar
20
+ end
21
+ end
22
+ ```
23
+
24
+ So, when you start your app, "Bar is not a class" exception will be thrown. autoload_checker
25
+ detects conflict definitions and fix them by replacing `module Bar` with `class Bar`
26
+ in `foo/bar/baz.rb`.
27
+
28
+ # Usage
29
+
30
+ ```
31
+ Usage: ./autoload_checker.rb [options]
32
+ -p, --path FILE [Mandatory] file or directory to check
33
+ -c, --correct Enable errors correction
34
+ -h, --help Prints this help
35
+ ```
@@ -0,0 +1,33 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'pathname'
4
+ $:.unshift Pathname.new(File.expand_path(__dir__)).parent.join('lib')
5
+
6
+ require 'optparse'
7
+ require 'namespace'
8
+ require 'autoload_checker'
9
+
10
+ Options = Struct.new(:dir, :correct)
11
+
12
+ options = Options.new(nil, false)
13
+ opt_parser = OptionParser.new do |opts|
14
+ opts.banner = "Usage: #{$0} [options]"
15
+ opts.on('-p FILE', '--path', String, '[Mandatory] file or directory to check') do |v|
16
+ options.dir = v
17
+ end
18
+ opts.on('-c', '--correct', TrueClass, 'Enable errors correction') { |v| options.correct = v }
19
+ opts.on('-h', '--help', 'Prints this help') do
20
+ puts opts
21
+ exit
22
+ end
23
+ end
24
+ opt_parser.parse!
25
+
26
+ unless options.dir
27
+ puts opt_parser.help
28
+ exit(1)
29
+ end
30
+
31
+ autoload_checker = AutoloadChecker.new(path: options.dir, correct: options.correct)
32
+ autoload_checker.call
33
+ exit(1) if autoload_checker.fixes.any? && !options.correct
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ class AutoloadChecker
4
+ def initialize(path:, correct:, output: $stderr)
5
+ @path = path
6
+ @correct = correct
7
+ @output = output
8
+ end
9
+
10
+ def call
11
+ validator.call
12
+ return if fixes.empty?
13
+
14
+ if correct?
15
+ fixes.each { |file, fixes| Namespace::Corrector.new(file: file, fixes: fixes).call }
16
+ else
17
+ validator.fixes.each do |_file, fixes|
18
+ fixes.each { |fix| @output.puts(fix.pretty_error) }
19
+ end
20
+ end
21
+ end
22
+
23
+ def fixes
24
+ validator.fixes
25
+ end
26
+
27
+ private
28
+
29
+ def validator
30
+ @validator ||= Namespace::Validator.new(@path)
31
+ end
32
+
33
+ def correct?
34
+ @correct
35
+ end
36
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal
2
+
3
+ class Namespace
4
+ class Corrector
5
+ def initialize(file:, fixes:)
6
+ @file = file
7
+ @fixes = fixes
8
+ end
9
+
10
+ def call
11
+ content = File.read(@file)
12
+ @fixes.each do |fix|
13
+ content.gsub!(/#{fix.from} #{fix.const_name}/, "#{fix.to} #{fix.const_name}")
14
+ end
15
+ File.write(@file, content)
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Namespace
4
+ class Fix
5
+ attr_reader :file, :const_name, :from, :to
6
+
7
+ def initialize(file:, const_name:, from:, to:)
8
+ @file = file
9
+ @const_name = const_name
10
+ @from = from
11
+ @to = to
12
+ end
13
+
14
+ def pretty_error
15
+ "#{@file}: #{const_name} must be a #{@to} not a #{@from}"
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'ripper'
4
+ require 'namespace'
5
+
6
+ class Namespace
7
+ class NonRootAstError < StandardError; end
8
+ class ConstRefExpectedError < StandardError; end
9
+
10
+ class Parser
11
+ def initialize(file)
12
+ @file = file
13
+ end
14
+
15
+ def call
16
+ @ast = File.open(@file) { |fd| Ripper.sexp(fd, @file) }
17
+ raise NonRootAstError, @file unless @ast[0] == :program
18
+
19
+ module_or_class_defs(@ast[1])
20
+ end
21
+
22
+ private
23
+
24
+ def module_or_class_defs(ast)
25
+ ast.each_with_object({}) do |stmt, namespaces|
26
+ next if !stmt.is_a?(Array) || !%i[module class].include?(stmt[0])
27
+
28
+ type = stmt[0]
29
+ const_ref = stmt[1]
30
+ raise ConstRefExpectedError unless %i[const_ref const_path_ref].include?(const_ref[0])
31
+
32
+ next if const_ref[0] == :const_path_ref
33
+
34
+ name = const_ref[1][1]
35
+ pos = const_ref[1][2]
36
+ line = pos[0]
37
+ column = pos[1]
38
+
39
+ children = module_or_class_defs(module_or_class_body(stmt))
40
+ namespaces[name] = Namespace.new(
41
+ type: type, name: name, children: children, file: @file, line: line, column: column
42
+ )
43
+ end
44
+ end
45
+
46
+ def module_or_class_body(ast)
47
+ index = ast.find_index { |stmt| stmt.is_a?(Array) && stmt[0] == :bodystmt }
48
+ ast[index][1]
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,70 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'pathname'
4
+ require 'namespace/fix'
5
+ require 'namespace/parser'
6
+
7
+ class Namespace
8
+ class Validator
9
+ attr_reader :fixes, :namespaces
10
+
11
+ def initialize(root_dir)
12
+ @root_dir = root_dir
13
+ @namespaces = {}
14
+ @fixes = Hash.new { |hsh, k| hsh[k] = [] }
15
+ end
16
+
17
+ def call
18
+ directories = [@root_dir]
19
+ new_directories = []
20
+
21
+ while directories.any?
22
+ dir = directories.pop
23
+ Pathname.glob(File.join(dir, '*')) do |file|
24
+ if file.directory?
25
+ new_directories << file
26
+ elsif file.extname == '.rb'
27
+ namespace = Namespace::Parser.new(file.to_s).call
28
+ add_namespace(namespace)
29
+ end
30
+ end
31
+
32
+ if directories.empty?
33
+ directories = new_directories
34
+ new_directories = []
35
+ end
36
+ end
37
+ end
38
+
39
+ private
40
+
41
+ def add_namespace(namespace)
42
+ merge_children(@namespaces, namespace)
43
+ end
44
+
45
+ def merge_children(children1, children2)
46
+ children2.each do |k, space2|
47
+ space1 = children1[k]
48
+ if !space1
49
+ if space2.class? && !space2.has_standalone_file? && space2.children.any?
50
+ @fixes[space2.file] << Fix.new(
51
+ file: space2.file, const_name: k, from: :class, to: :module
52
+ )
53
+ space2.type = :module
54
+ end
55
+
56
+ children1[k] = space2
57
+ next
58
+ end
59
+
60
+ if space1.type != space2.type
61
+ @fixes[space2.file] << Fix.new(
62
+ file: space2.file, const_name: k, from: space2.type, to: space1.type
63
+ )
64
+ end
65
+
66
+ merge_children(space1.children, space2.children)
67
+ end
68
+ end
69
+ end
70
+ end
data/lib/namespace.rb ADDED
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'pathname'
4
+ require 'namespace/corrector'
5
+ require 'namespace/fix'
6
+ require 'namespace/parser'
7
+ require 'namespace/validator'
8
+
9
+ class Namespace
10
+ attr_reader :name, :children, :file, :line, :column
11
+ attr_accessor :type
12
+
13
+ def initialize(type:, name:, children:, file:, line:, column:)
14
+ @type = type
15
+ @name = name
16
+ @children = children
17
+ @file = file
18
+ @line = line
19
+ @column = column
20
+ end
21
+
22
+ def has_standalone_file?
23
+ Pathname.new(@file).basename.to_s == to_snake_case(@name) + ".rb"
24
+ end
25
+
26
+ def class?
27
+ @type == :class
28
+ end
29
+
30
+ def module?
31
+ @type == :module
32
+ end
33
+
34
+ private
35
+
36
+ def to_snake_case(str)
37
+ str.gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2')
38
+ .gsub(/([a-z\d])([A-Z])/,'\1_\2')
39
+ .tr('-', '_')
40
+ .downcase
41
+ end
42
+ end
metadata ADDED
@@ -0,0 +1,69 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: autoload-checker
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - cyberfined
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2022-06-30 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '2.0'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '2.0'
27
+ description: Checks for conflicts in class/module definitions and corrects them.
28
+ email:
29
+ - cyberfined@protonmail.com
30
+ executables:
31
+ - autoload_checker.rb
32
+ extensions: []
33
+ extra_rdoc_files: []
34
+ files:
35
+ - Gemfile
36
+ - Gemfile.lock
37
+ - LICENSE
38
+ - README.md
39
+ - bin/autoload_checker.rb
40
+ - lib/autoload_checker.rb
41
+ - lib/namespace.rb
42
+ - lib/namespace/corrector.rb
43
+ - lib/namespace/fix.rb
44
+ - lib/namespace/parser.rb
45
+ - lib/namespace/validator.rb
46
+ homepage: https://github.com/cyberfined/autoload-checker
47
+ licenses:
48
+ - WTFPL
49
+ metadata: {}
50
+ post_install_message:
51
+ rdoc_options: []
52
+ require_paths:
53
+ - lib
54
+ required_ruby_version: !ruby/object:Gem::Requirement
55
+ requirements:
56
+ - - ">="
57
+ - !ruby/object:Gem::Version
58
+ version: '2.5'
59
+ required_rubygems_version: !ruby/object:Gem::Requirement
60
+ requirements:
61
+ - - ">="
62
+ - !ruby/object:Gem::Version
63
+ version: '0'
64
+ requirements: []
65
+ rubygems_version: 3.3.11
66
+ signing_key:
67
+ specification_version: 4
68
+ summary: Checks for conflicts in class/module definitions and corrects them.
69
+ test_files: []