rubocop-sketchup 1.2.0 → 1.4.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.
Files changed (70) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +20 -19
  3. data/assets/output.html.erb +301 -301
  4. data/config/default.yml +379 -379
  5. data/lib/rubocop/sketchup/config.rb +63 -63
  6. data/lib/rubocop/sketchup/cop/bugs/material_name.rb +108 -108
  7. data/lib/rubocop/sketchup/cop/bugs/render_mode.rb +72 -72
  8. data/lib/rubocop/sketchup/cop/bugs/uniform_scaling.rb +36 -36
  9. data/lib/rubocop/sketchup/cop/deprecations/add_separator_to_menu.rb +25 -25
  10. data/lib/rubocop/sketchup/cop/deprecations/operation_next_transparent.rb +30 -30
  11. data/lib/rubocop/sketchup/cop/deprecations/require_all.rb +27 -27
  12. data/lib/rubocop/sketchup/cop/deprecations/set_texture_projection.rb +26 -26
  13. data/lib/rubocop/sketchup/cop/deprecations/show_ruby_panel.rb +25 -25
  14. data/lib/rubocop/sketchup/cop/deprecations/sketchup_set.rb +30 -30
  15. data/lib/rubocop/sketchup/cop/performance/openssl.rb +41 -41
  16. data/lib/rubocop/sketchup/cop/performance/operation_disable_ui.rb +33 -33
  17. data/lib/rubocop/sketchup/cop/performance/selection_bulk.rb +79 -79
  18. data/lib/rubocop/sketchup/cop/performance/type_check.rb +63 -63
  19. data/lib/rubocop/sketchup/cop/performance/typename.rb +24 -24
  20. data/lib/rubocop/sketchup/cop/requirements/api_namespace.rb +30 -30
  21. data/lib/rubocop/sketchup/cop/requirements/exit.rb +33 -33
  22. data/lib/rubocop/sketchup/cop/requirements/extension_namespace.rb +125 -125
  23. data/lib/rubocop/sketchup/cop/requirements/file_structure.rb +97 -97
  24. data/lib/rubocop/sketchup/cop/requirements/gem_install.rb +45 -45
  25. data/lib/rubocop/sketchup/cop/requirements/get_extension_license.rb +95 -95
  26. data/lib/rubocop/sketchup/cop/requirements/global_constants.rb +38 -38
  27. data/lib/rubocop/sketchup/cop/requirements/global_include.rb +42 -42
  28. data/lib/rubocop/sketchup/cop/requirements/global_methods.rb +65 -65
  29. data/lib/rubocop/sketchup/cop/requirements/global_variables.rb +95 -95
  30. data/lib/rubocop/sketchup/cop/requirements/language_handler_globals.rb +46 -46
  31. data/lib/rubocop/sketchup/cop/requirements/load_path.rb +83 -83
  32. data/lib/rubocop/sketchup/cop/requirements/minimal_registration.rb +73 -73
  33. data/lib/rubocop/sketchup/cop/requirements/observers_start_operation.rb +161 -161
  34. data/lib/rubocop/sketchup/cop/requirements/register_extension.rb +45 -45
  35. data/lib/rubocop/sketchup/cop/requirements/ruby_core_namespace.rb +291 -291
  36. data/lib/rubocop/sketchup/cop/requirements/ruby_stdlib_namespace.rb +634 -634
  37. data/lib/rubocop/sketchup/cop/requirements/shipped_extensions_namespace.rb +61 -61
  38. data/lib/rubocop/sketchup/cop/requirements/sketchup_extension.rb +119 -119
  39. data/lib/rubocop/sketchup/cop/requirements/sketchup_require.rb +150 -149
  40. data/lib/rubocop/sketchup/cop/suggestions/add_group.rb +49 -49
  41. data/lib/rubocop/sketchup/cop/suggestions/compatibility.rb +128 -128
  42. data/lib/rubocop/sketchup/cop/suggestions/dc_internals.rb +34 -34
  43. data/lib/rubocop/sketchup/cop/suggestions/file_encoding.rb +78 -78
  44. data/lib/rubocop/sketchup/cop/suggestions/model_entities.rb +58 -58
  45. data/lib/rubocop/sketchup/cop/suggestions/monkey_patched_api.rb +45 -45
  46. data/lib/rubocop/sketchup/cop/suggestions/operation_name.rb +137 -137
  47. data/lib/rubocop/sketchup/cop/suggestions/sketchup_find_support_file.rb +39 -39
  48. data/lib/rubocop/sketchup/cop/suggestions/tool_drawing_bounds.rb +45 -45
  49. data/lib/rubocop/sketchup/cop/suggestions/tool_invalidate.rb +68 -68
  50. data/lib/rubocop/sketchup/cop/suggestions/tool_user_input.rb +41 -41
  51. data/lib/rubocop/sketchup/cop/suggestions/toolbar_timer.rb +65 -65
  52. data/lib/rubocop/sketchup/cop.rb +110 -110
  53. data/lib/rubocop/sketchup/dc_globals.rb +24 -24
  54. data/lib/rubocop/sketchup/dc_methods.rb +130 -130
  55. data/lib/rubocop/sketchup/extension_project.rb +65 -65
  56. data/lib/rubocop/sketchup/features.rb +1477 -1420
  57. data/lib/rubocop/sketchup/formatter/extension_review.rb +269 -269
  58. data/lib/rubocop/sketchup/inject.rb +19 -19
  59. data/lib/rubocop/sketchup/namespace.rb +49 -49
  60. data/lib/rubocop/sketchup/namespace_checker.rb +103 -46
  61. data/lib/rubocop/sketchup/no_comment_disable.rb +17 -17
  62. data/lib/rubocop/sketchup/range_help.rb +52 -52
  63. data/lib/rubocop/sketchup/sketchup_target_range.rb +75 -75
  64. data/lib/rubocop/sketchup/sketchup_version.rb +129 -128
  65. data/lib/rubocop/sketchup/tool_checker.rb +41 -43
  66. data/lib/rubocop/sketchup/version.rb +7 -7
  67. data/lib/rubocop/sketchup.rb +14 -14
  68. data/lib/rubocop-sketchup.rb +53 -53
  69. data/rubocop-sketchup.gemspec +29 -29
  70. metadata +8 -8
@@ -1,24 +1,24 @@
1
- # frozen_string_literal: true
2
-
3
- module RuboCop
4
- module Cop
5
- module SketchupPerformance
6
- # `.typename` is very slow, prefer `.is_a?` instead.
7
- #
8
- # `entity.typename == 'Face'` is slow because it performs a string
9
- # comparison. `is_a?` is much faster because it's a simple type check.
10
- class Typename < SketchUp::Cop
11
- MSG = '`.typename` is very slow, prefer `.is_a?` instead.'
12
-
13
- def on_send(node)
14
- _, method_name = *node
15
- return unless method_name == :typename
16
-
17
- # TODO(thomthom): Should we try to detect use of #typename
18
- # in context of comparing against a string?
19
- add_offense(node, location: :selector)
20
- end
21
- end
22
- end
23
- end
24
- end
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module SketchupPerformance
6
+ # `.typename` is very slow, prefer `.is_a?` instead.
7
+ #
8
+ # `entity.typename == 'Face'` is slow because it performs a string
9
+ # comparison. `is_a?` is much faster because it's a simple type check.
10
+ class Typename < SketchUp::Cop
11
+ MSG = '`.typename` is very slow, prefer `.is_a?` instead.'
12
+
13
+ def on_send(node)
14
+ _, method_name = *node
15
+ return unless method_name == :typename
16
+
17
+ # TODO(thomthom): Should we try to detect use of #typename
18
+ # in context of comparing against a string?
19
+ add_offense(node, location: :selector)
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -1,30 +1,30 @@
1
- # frozen_string_literal: true
2
-
3
- module RuboCop
4
- module Cop
5
- module SketchupRequirements
6
- # Do not modify the Sketch API. This will affect other extensions and
7
- # very likely cause them to fail.
8
- #
9
- # This requirement also include adding things into the SketchUp API
10
- # namespace. The API namespace is reserved for future additions to the
11
- # API.
12
- class ApiNamespace < SketchUp::Cop
13
-
14
- include SketchUp::NoCommentDisable
15
- include SketchUp::NamespaceChecker
16
-
17
- MSG = 'Do not modify the SketchUp API.'
18
-
19
- NAMESPACES = %w[
20
- Geom Layout Sketchup SketchupExtension UI
21
- ].freeze
22
-
23
- def namespaces
24
- NAMESPACES
25
- end
26
-
27
- end
28
- end
29
- end
30
- end
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module SketchupRequirements
6
+ # Do not modify the Sketch API. This will affect other extensions and
7
+ # very likely cause them to fail.
8
+ #
9
+ # This requirement also include adding things into the SketchUp API
10
+ # namespace. The API namespace is reserved for future additions to the
11
+ # API.
12
+ class ApiNamespace < SketchUp::Cop
13
+
14
+ include SketchUp::NoCommentDisable
15
+ include SketchUp::NamespaceChecker
16
+
17
+ MSG = 'Do not modify the SketchUp API.'
18
+
19
+ NAMESPACES = %w[
20
+ Geom Layout Sketchup SketchupExtension UI
21
+ ].freeze
22
+
23
+ def namespaces
24
+ NAMESPACES
25
+ end
26
+
27
+ end
28
+ end
29
+ end
30
+ end
@@ -1,33 +1,33 @@
1
- # frozen_string_literal: true
2
-
3
- module RuboCop
4
- module Cop
5
- module SketchupRequirements
6
- # Don't attempt to kill the Ruby interpreter by calling `exit` or `exit!`.
7
- # SketchUp will trap `exit` and prevent that, with a message in the
8
- # console. But `exit!` is not trapped and will terminate SketchUp without
9
- # shutting down cleanly.
10
- #
11
- # Use `return`, `next`, `break` or `raise` instead.
12
- class Exit < SketchUp::Cop
13
-
14
- include SketchUp::NoCommentDisable
15
-
16
- MSG = '`%s` attempts to kill the Ruby interpreter. Use `return`, '\
17
- '`next`, `break` or `raise` instead.'
18
-
19
- # Reference: http://rubocop.readthedocs.io/en/latest/development/
20
- def_node_matcher :exit?, <<-PATTERN
21
- (send {(const nil? :Kernel) nil?} {:abort :exit :exit!} ...)
22
- PATTERN
23
-
24
- def on_send(node)
25
- return unless exit?(node)
26
-
27
- message = format(MSG, node.method_name)
28
- add_offense(node, location: :selector, message: message)
29
- end
30
- end
31
- end
32
- end
33
- end
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module SketchupRequirements
6
+ # Don't attempt to kill the Ruby interpreter by calling `exit` or `exit!`.
7
+ # SketchUp will trap `exit` and prevent that, with a message in the
8
+ # console. But `exit!` is not trapped and will terminate SketchUp without
9
+ # shutting down cleanly.
10
+ #
11
+ # Use `return`, `next`, `break` or `raise` instead.
12
+ class Exit < SketchUp::Cop
13
+
14
+ include SketchUp::NoCommentDisable
15
+
16
+ MSG = '`%s` attempts to kill the Ruby interpreter. Use `return`, ' \
17
+ '`next`, `break` or `raise` instead.'
18
+
19
+ # Reference: http://rubocop.readthedocs.io/en/latest/development/
20
+ def_node_matcher :exit?, <<-PATTERN
21
+ (send {(const nil? :Kernel) nil?} {:abort :exit :exit!} ...)
22
+ PATTERN
23
+
24
+ def on_send(node)
25
+ return unless exit?(node)
26
+
27
+ message = format(MSG, node.method_name)
28
+ add_offense(node, location: :selector, message: message)
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -1,125 +1,125 @@
1
- # frozen_string_literal: true
2
-
3
- require 'rubocop/sketchup/cop/requirements/api_namespace'
4
- require 'rubocop/sketchup/cop/requirements/ruby_core_namespace'
5
- require 'rubocop/sketchup/cop/requirements/ruby_stdlib_namespace'
6
-
7
- module RuboCop
8
- module Cop
9
- module SketchupRequirements
10
- # Extensions in SketchUp all share the same Ruby environment on the user's
11
- # machine. Because of this it's important that each extension isolate
12
- # itself to avoid clashing with other extensions.
13
- #
14
- # Extensions submitted to Extension Warehouse is expected to use only one
15
- # root module.
16
- #
17
- # @example Good - this contains everything in the extension.
18
- # module MyExtension
19
- # class Foo
20
- # end
21
- # class Bar
22
- # end
23
- # end
24
- #
25
- # @example Better - this further reduce chance of clashing.
26
- # module MyCompany
27
- # module MyExtension
28
- # class Foo
29
- # end
30
- # class Bar
31
- # end
32
- # end
33
- # end
34
- class ExtensionNamespace < SketchUp::Cop
35
-
36
- include SketchUp::NoCommentDisable
37
- include SketchUp
38
-
39
- def on_class(node)
40
- check_class_or_module(node)
41
- end
42
-
43
- def on_module(node)
44
- check_class_or_module(node)
45
- end
46
-
47
- def check_class_or_module(node)
48
- name = node.defined_module_name
49
-
50
- if node.parent_module_name
51
- parent = Namespace.new(node.parent_module_name)
52
- else
53
- # This is somewhat of an educated guess. We might end up here with
54
- # code like this:
55
- #
56
- # Example.generate do
57
- # module HelloWorld
58
- # end
59
- # end
60
- #
61
- # It might be that the module is evaluated in a different context.
62
- # But we'll accept the possible false positive and let the user
63
- # config exceptions if needed.
64
- parent = Namespace.new('Object')
65
- end
66
- namespace = parent.join(name)
67
-
68
- # Don't want to process anything that aren't top level namespaces.
69
- return unless parent.top_level?
70
- # Don't check excluded namespaces.
71
- return if exempted?(namespace)
72
-
73
- check_namespace(node, namespace)
74
- end
75
-
76
- # Class variables are normally frowned upon since they leak through all
77
- # instances. However, in this case this is exactly what we want.
78
- # The Cop picks up the first top level namespace it encounters and then
79
- # keep track of whether it detects more top level namespaces.
80
- @@namespace = nil
81
- def check_namespace(node, namespace)
82
- # Make sure the namespace isn't part of reserved namespaces that other
83
- # cops are checking.
84
- return if reserved?(namespace)
85
-
86
- # Remember the first namespace encountered and log an offence if
87
- # more top level namespaces are registered.
88
- top = namespace.first
89
- @@namespace ||= top
90
- return if @@namespace == top
91
-
92
- add_offense(node, location: :name)
93
- end
94
-
95
- def reserved?(namespace)
96
- top = namespace.first
97
- return true if RubyCoreNamespace::NAMESPACES.include?(top)
98
- return true if RubyStdLibNamespace::NAMESPACES.include?(top)
99
- return true if ApiNamespace::NAMESPACES.include?(top)
100
-
101
- false
102
- end
103
-
104
- def message(node)
105
- namespace = Namespace.new(node.defined_module_name).from_root
106
- format('Use a single root namespace. '\
107
- '(Found `%<found>s`; Previously found `%<expected>s`)',
108
- found: namespace, expected: @@namespace)
109
- end
110
-
111
- def exempted?(namespace)
112
- namespace_exceptions.include?(namespace.first)
113
- end
114
-
115
- def namespace_exceptions
116
- exceptions = cop_config['Exceptions'] || []
117
- return exceptions if exceptions.is_a?(Array)
118
-
119
- raise 'exceptions needs to be an array of strings!'
120
- end
121
-
122
- end
123
- end
124
- end
125
- end
1
+ # frozen_string_literal: true
2
+
3
+ require 'rubocop/sketchup/cop/requirements/api_namespace'
4
+ require 'rubocop/sketchup/cop/requirements/ruby_core_namespace'
5
+ require 'rubocop/sketchup/cop/requirements/ruby_stdlib_namespace'
6
+
7
+ module RuboCop
8
+ module Cop
9
+ module SketchupRequirements
10
+ # Extensions in SketchUp all share the same Ruby environment on the user's
11
+ # machine. Because of this it's important that each extension isolate
12
+ # itself to avoid clashing with other extensions.
13
+ #
14
+ # Extensions submitted to Extension Warehouse is expected to use only one
15
+ # root module.
16
+ #
17
+ # @example Good - this contains everything in the extension.
18
+ # module MyExtension
19
+ # class Foo
20
+ # end
21
+ # class Bar
22
+ # end
23
+ # end
24
+ #
25
+ # @example Better - this further reduce chance of clashing.
26
+ # module MyCompany
27
+ # module MyExtension
28
+ # class Foo
29
+ # end
30
+ # class Bar
31
+ # end
32
+ # end
33
+ # end
34
+ class ExtensionNamespace < SketchUp::Cop
35
+
36
+ include SketchUp::NoCommentDisable
37
+ include SketchUp
38
+
39
+ def on_class(node)
40
+ check_class_or_module(node)
41
+ end
42
+
43
+ def on_module(node)
44
+ check_class_or_module(node)
45
+ end
46
+
47
+ def check_class_or_module(node)
48
+ name = node.defined_module_name
49
+
50
+ if node.parent_module_name
51
+ parent = Namespace.new(node.parent_module_name)
52
+ else
53
+ # This is somewhat of an educated guess. We might end up here with
54
+ # code like this:
55
+ #
56
+ # Example.generate do
57
+ # module HelloWorld
58
+ # end
59
+ # end
60
+ #
61
+ # It might be that the module is evaluated in a different context.
62
+ # But we'll accept the possible false positive and let the user
63
+ # config exceptions if needed.
64
+ parent = Namespace.new('Object')
65
+ end
66
+ namespace = parent.join(name)
67
+
68
+ # Don't want to process anything that aren't top level namespaces.
69
+ return unless parent.top_level?
70
+ # Don't check excluded namespaces.
71
+ return if exempted?(namespace)
72
+
73
+ check_namespace(node, namespace)
74
+ end
75
+
76
+ # Class variables are normally frowned upon since they leak through all
77
+ # instances. However, in this case this is exactly what we want.
78
+ # The Cop picks up the first top level namespace it encounters and then
79
+ # keep track of whether it detects more top level namespaces.
80
+ @@namespace = nil
81
+ def check_namespace(node, namespace)
82
+ # Make sure the namespace isn't part of reserved namespaces that other
83
+ # cops are checking.
84
+ return if reserved?(namespace)
85
+
86
+ # Remember the first namespace encountered and log an offence if
87
+ # more top level namespaces are registered.
88
+ top = namespace.first
89
+ @@namespace ||= top
90
+ return if @@namespace == top
91
+
92
+ add_offense(node, location: :name)
93
+ end
94
+
95
+ def reserved?(namespace)
96
+ top = namespace.first
97
+ return true if RubyCoreNamespace::NAMESPACES.include?(top)
98
+ return true if RubyStdLibNamespace::NAMESPACES.include?(top)
99
+ return true if ApiNamespace::NAMESPACES.include?(top)
100
+
101
+ false
102
+ end
103
+
104
+ def message(node)
105
+ namespace = Namespace.new(node.defined_module_name).from_root
106
+ format('Use a single root namespace. ' \
107
+ '(Found `%<found>s`; Previously found `%<expected>s`)',
108
+ found: namespace, expected: @@namespace)
109
+ end
110
+
111
+ def exempted?(namespace)
112
+ namespace_exceptions.include?(namespace.first)
113
+ end
114
+
115
+ def namespace_exceptions
116
+ exceptions = cop_config['Exceptions'] || []
117
+ return exceptions if exceptions.is_a?(Array)
118
+
119
+ raise 'exceptions needs to be an array of strings!'
120
+ end
121
+
122
+ end
123
+ end
124
+ end
125
+ end
@@ -1,97 +1,97 @@
1
- # frozen_string_literal: true
2
-
3
- module RuboCop
4
- module Cop
5
- module SketchupRequirements
6
- # Check that the extension conform to expected file structure with a
7
- # single root .rb file and a support folder with matching name.
8
- #
9
- # Make sure to match upper and lower case characters between the root .rb
10
- # file and the support folder.
11
- #
12
- # @example
13
- # SketchUp/Plugins
14
- # + ex_hello_world.rb
15
- # + ex_hello_world
16
- # + main.rb
17
- # + ...
18
- class FileStructure < SketchUp::Cop
19
-
20
- include SketchUp::NoCommentDisable
21
- include SketchUp::ExtensionProject
22
- include RangeHelp
23
-
24
- IGNORED_DIRECTORIES = %w[
25
- __MACOSX
26
- ].freeze
27
-
28
- def investigate(processed_source)
29
- return if already_run?
30
-
31
- # Using range similar to RuboCop::Cop::Naming::Filename (file_name.rb)
32
- range = source_range(processed_source.buffer, 1, 0)
33
-
34
- # Find all root Ruby files in the source directory.
35
- pattern = "#{source_path}/*.rb"
36
- root_ruby_files = Dir.glob(pattern)
37
-
38
- # Ensure there is only one root Ruby file.
39
- if root_ruby_files.size != 1
40
- msg = 'Extensions must have exactly one root Ruby (.rb) file. '\
41
- 'Found: %d'
42
- add_offense(nil,
43
- location: range,
44
- message: format(msg, root_ruby_files.size))
45
- return
46
- end
47
-
48
- # Find the root file and collect the sub-directories.
49
- root_file = root_ruby_files.first
50
- extension_basename = File.basename(root_file, '.*')
51
- sub_folders = source_path.children.select(&:directory?)
52
- sub_folders.reject! { |folder|
53
- IGNORED_DIRECTORIES.include?(folder.basename.to_s)
54
- }
55
-
56
- # Ensure there is only one sub-directory.
57
- if sub_folders.size != 1
58
- msg = 'Extensions must have exactly one support directory. Found %d'
59
- add_offense(nil,
60
- location: range,
61
- message: format(msg, sub_folders.size))
62
- return
63
- end
64
-
65
- # Ensure support directory's name match the root Ruby file.
66
- support_directory = sub_folders.first
67
- unless support_directory.basename.to_s == extension_basename
68
- msg = 'Extensions must have a support directory matching the name '\
69
- 'of the root Ruby file. Expected %s, found %s'
70
- msg = format(msg, extension_basename, support_directory.basename)
71
- add_offense(nil,
72
- location: range,
73
- message: msg)
74
- end
75
- end
76
-
77
- private
78
-
79
- @@already_run = false
80
-
81
- def already_run?
82
- return true if @@already_run
83
-
84
- @@already_run = true
85
- false
86
- end
87
-
88
- # rubocop:disable Lint/IneffectiveAccessModifier
89
- def self.reset
90
- @@already_run = false
91
- end
92
- # rubocop:enable Lint/IneffectiveAccessModifier
93
-
94
- end
95
- end
96
- end
97
- end
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module SketchupRequirements
6
+ # Check that the extension conform to expected file structure with a
7
+ # single root .rb file and a support folder with matching name.
8
+ #
9
+ # Make sure to match upper and lower case characters between the root .rb
10
+ # file and the support folder.
11
+ #
12
+ # @example
13
+ # SketchUp/Plugins
14
+ # + ex_hello_world.rb
15
+ # + ex_hello_world
16
+ # + main.rb
17
+ # + ...
18
+ class FileStructure < SketchUp::Cop
19
+
20
+ include SketchUp::NoCommentDisable
21
+ include SketchUp::ExtensionProject
22
+ include RangeHelp
23
+
24
+ IGNORED_DIRECTORIES = %w[
25
+ __MACOSX
26
+ ].freeze
27
+
28
+ def investigate(processed_source)
29
+ return if already_run?
30
+
31
+ # Using range similar to RuboCop::Cop::Naming::Filename (file_name.rb)
32
+ range = source_range(processed_source.buffer, 1, 0)
33
+
34
+ # Find all root Ruby files in the source directory.
35
+ pattern = "#{source_path}/*.rb"
36
+ root_ruby_files = Dir.glob(pattern)
37
+
38
+ # Ensure there is only one root Ruby file.
39
+ if root_ruby_files.size != 1
40
+ msg = 'Extensions must have exactly one root Ruby (.rb) file. ' \
41
+ 'Found: %d'
42
+ add_offense(nil,
43
+ location: range,
44
+ message: format(msg, root_ruby_files.size))
45
+ return
46
+ end
47
+
48
+ # Find the root file and collect the sub-directories.
49
+ root_file = root_ruby_files.first
50
+ extension_basename = File.basename(root_file, '.*')
51
+ sub_folders = source_path.children.select(&:directory?)
52
+ sub_folders.reject! { |folder|
53
+ IGNORED_DIRECTORIES.include?(folder.basename.to_s)
54
+ }
55
+
56
+ # Ensure there is only one sub-directory.
57
+ if sub_folders.size != 1
58
+ msg = 'Extensions must have exactly one support directory. Found %d'
59
+ add_offense(nil,
60
+ location: range,
61
+ message: format(msg, sub_folders.size))
62
+ return
63
+ end
64
+
65
+ # Ensure support directory's name match the root Ruby file.
66
+ support_directory = sub_folders.first
67
+ unless support_directory.basename.to_s == extension_basename
68
+ msg = 'Extensions must have a support directory matching the ' \
69
+ 'name of the root Ruby file. Expected %s, found %s'
70
+ msg = format(msg, extension_basename, support_directory.basename)
71
+ add_offense(nil,
72
+ location: range,
73
+ message: msg)
74
+ end
75
+ end
76
+
77
+ private
78
+
79
+ @@already_run = false
80
+
81
+ def already_run?
82
+ return true if @@already_run
83
+
84
+ @@already_run = true
85
+ false
86
+ end
87
+
88
+ # rubocop:disable Lint/IneffectiveAccessModifier
89
+ def self.reset
90
+ @@already_run = false
91
+ end
92
+ # rubocop:enable Lint/IneffectiveAccessModifier
93
+
94
+ end
95
+ end
96
+ end
97
+ end