autoload-checker 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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: []