rubocop-sketchup 0.5.0 → 0.6.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 (66) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +19 -19
  3. data/assets/output.html.erb +301 -301
  4. data/config/default.yml +355 -355
  5. data/lib/rubocop/sketchup/config.rb +63 -63
  6. data/lib/rubocop/sketchup/cop/deprecations/add_separator_to_menu.rb +25 -25
  7. data/lib/rubocop/sketchup/cop/deprecations/operation_next_transparent.rb +30 -30
  8. data/lib/rubocop/sketchup/cop/deprecations/require_all.rb +27 -27
  9. data/lib/rubocop/sketchup/cop/deprecations/set_texture_projection.rb +26 -26
  10. data/lib/rubocop/sketchup/cop/deprecations/show_ruby_panel.rb +25 -25
  11. data/lib/rubocop/sketchup/cop/deprecations/sketchup_set.rb +30 -30
  12. data/lib/rubocop/sketchup/cop/performance/openssl.rb +41 -41
  13. data/lib/rubocop/sketchup/cop/performance/operation_disable_ui.rb +33 -33
  14. data/lib/rubocop/sketchup/cop/performance/selection_bulk.rb +79 -79
  15. data/lib/rubocop/sketchup/cop/performance/type_check.rb +63 -63
  16. data/lib/rubocop/sketchup/cop/performance/typename.rb +24 -24
  17. data/lib/rubocop/sketchup/cop/requirements/api_namespace.rb +30 -30
  18. data/lib/rubocop/sketchup/cop/requirements/exit.rb +32 -32
  19. data/lib/rubocop/sketchup/cop/requirements/extension_namespace.rb +108 -108
  20. data/lib/rubocop/sketchup/cop/requirements/file_structure.rb +97 -97
  21. data/lib/rubocop/sketchup/cop/requirements/gem_install.rb +45 -45
  22. data/lib/rubocop/sketchup/cop/requirements/get_extension_license.rb +95 -95
  23. data/lib/rubocop/sketchup/cop/requirements/global_constants.rb +38 -38
  24. data/lib/rubocop/sketchup/cop/requirements/global_include.rb +42 -42
  25. data/lib/rubocop/sketchup/cop/requirements/global_methods.rb +65 -65
  26. data/lib/rubocop/sketchup/cop/requirements/global_variables.rb +95 -95
  27. data/lib/rubocop/sketchup/cop/requirements/language_handler_globals.rb +46 -46
  28. data/lib/rubocop/sketchup/cop/requirements/load_path.rb +83 -83
  29. data/lib/rubocop/sketchup/cop/requirements/minimal_registration.rb +73 -73
  30. data/lib/rubocop/sketchup/cop/requirements/observers_start_operation.rb +161 -161
  31. data/lib/rubocop/sketchup/cop/requirements/register_extension.rb +45 -45
  32. data/lib/rubocop/sketchup/cop/requirements/ruby_core_namespace.rb +291 -291
  33. data/lib/rubocop/sketchup/cop/requirements/ruby_stdlib_namespace.rb +634 -634
  34. data/lib/rubocop/sketchup/cop/requirements/shipped_extensions_namespace.rb +61 -61
  35. data/lib/rubocop/sketchup/cop/requirements/sketchup_extension.rb +119 -119
  36. data/lib/rubocop/sketchup/cop/requirements/sketchup_require.rb +163 -163
  37. data/lib/rubocop/sketchup/cop/suggestions/add_group.rb +49 -49
  38. data/lib/rubocop/sketchup/cop/suggestions/compatibility.rb +117 -117
  39. data/lib/rubocop/sketchup/cop/suggestions/dc_internals.rb +34 -34
  40. data/lib/rubocop/sketchup/cop/suggestions/file_encoding.rb +78 -78
  41. data/lib/rubocop/sketchup/cop/suggestions/model_entities.rb +58 -58
  42. data/lib/rubocop/sketchup/cop/suggestions/monkey_patched_api.rb +45 -45
  43. data/lib/rubocop/sketchup/cop/suggestions/operation_name.rb +103 -103
  44. data/lib/rubocop/sketchup/cop/suggestions/sketchup_find_support_file.rb +39 -39
  45. data/lib/rubocop/sketchup/cop/suggestions/tool_drawing_bounds.rb +44 -44
  46. data/lib/rubocop/sketchup/cop/suggestions/tool_invalidate.rb +66 -66
  47. data/lib/rubocop/sketchup/cop/suggestions/tool_user_input.rb +41 -41
  48. data/lib/rubocop/sketchup/cop/suggestions/toolbar_timer.rb +65 -65
  49. data/lib/rubocop/sketchup/cop.rb +111 -111
  50. data/lib/rubocop/sketchup/dc_globals.rb +24 -24
  51. data/lib/rubocop/sketchup/dc_methods.rb +130 -130
  52. data/lib/rubocop/sketchup/extension_project.rb +65 -65
  53. data/lib/rubocop/sketchup/features.rb +738 -738
  54. data/lib/rubocop/sketchup/formatter/extension_review.rb +259 -259
  55. data/lib/rubocop/sketchup/inject.rb +19 -19
  56. data/lib/rubocop/sketchup/namespace.rb +47 -47
  57. data/lib/rubocop/sketchup/namespace_checker.rb +46 -46
  58. data/lib/rubocop/sketchup/no_comment_disable.rb +17 -17
  59. data/lib/rubocop/sketchup/range_help.rb +52 -52
  60. data/lib/rubocop/sketchup/sketchup_version.rb +87 -87
  61. data/lib/rubocop/sketchup/tool_checker.rb +43 -43
  62. data/lib/rubocop/sketchup/version.rb +5 -5
  63. data/lib/rubocop/sketchup.rb +12 -12
  64. data/lib/rubocop-sketchup.rb +48 -48
  65. data/rubocop-sketchup.gemspec +27 -27
  66. metadata +4 -4
@@ -1,34 +1,34 @@
1
- # frozen_string_literal: true
2
-
3
- module RuboCop
4
- module Cop
5
- module SketchupSuggestions
6
- # Tapping into the internals of Dynamic Components is risky. It could
7
- # change at any time. If you create an extension that depend on the
8
- # internal logic of another extension you are at the mercy of change and
9
- # luck!
10
- class DynamicComponentInternals < SketchUp::Cop
11
-
12
- include SketchUp::DynamicComponentGlobals
13
-
14
- MSG = 'Avoid relying on internal logic of Dynamic Components.'.freeze
15
-
16
- def on_gvar(node)
17
- check_global(node)
18
- end
19
-
20
- def on_gvasgn(node)
21
- check_global(node)
22
- end
23
-
24
- def check_global(node)
25
- global_var, = *node
26
- return unless dc_global_var?(global_var)
27
-
28
- add_offense(node, location: :name, severity: :warning)
29
- end
30
-
31
- end
32
- end
33
- end
34
- end
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module SketchupSuggestions
6
+ # Tapping into the internals of Dynamic Components is risky. It could
7
+ # change at any time. If you create an extension that depend on the
8
+ # internal logic of another extension you are at the mercy of change and
9
+ # luck!
10
+ class DynamicComponentInternals < SketchUp::Cop
11
+
12
+ include SketchUp::DynamicComponentGlobals
13
+
14
+ MSG = 'Avoid relying on internal logic of Dynamic Components.'.freeze
15
+
16
+ def on_gvar(node)
17
+ check_global(node)
18
+ end
19
+
20
+ def on_gvasgn(node)
21
+ check_global(node)
22
+ end
23
+
24
+ def check_global(node)
25
+ global_var, = *node
26
+ return unless dc_global_var?(global_var)
27
+
28
+ add_offense(node, location: :name, severity: :warning)
29
+ end
30
+
31
+ end
32
+ end
33
+ end
34
+ end
@@ -1,78 +1,78 @@
1
- # frozen_string_literal: true
2
-
3
- module RuboCop
4
- module Cop
5
- module SketchupSuggestions
6
- # When using __FILE__ and __dir__, beware that Ruby doesn't apply the
7
- # correct encoding to the strings under Windows. When they contain
8
- # non-english characters it will lead to exceptions being raised when the
9
- # strings are used. Force encoding to work around this.
10
- #
11
- # @example Might fail
12
- # basename = File.basename(__FILE__, '.*')
13
- #
14
- # @example Workaround
15
- # file = __FILE__.dup
16
- # file.force_encoding('UTF-8') if file.respond_to?(:force_encoding)
17
- # basename = File.basename(file, '.*')
18
- class FileEncoding < SketchUp::Cop
19
-
20
- MSG = 'Beware encoding bug with `__FILE__` and `__dir__`.'.freeze
21
-
22
- def_node_matcher :file_loaded?, <<-PATTERN
23
- (send nil? {:file_loaded? :file_loaded} ...)
24
- PATTERN
25
-
26
- def_node_matcher :magic_dir?, <<-PATTERN
27
- (send nil? :__dir__)
28
- PATTERN
29
-
30
- def magic_file?(node)
31
- node.respond_to?(:str_type?) &&
32
- node.str_type? &&
33
- node.source_range.is?('__FILE__')
34
- end
35
-
36
- def magic_file_or_dir?(node)
37
- magic_file?(node) || magic_dir?(node)
38
- end
39
-
40
- def on_send(node)
41
- return if file_loaded?(node)
42
- return if node.arguments.none?(&method(:magic_file_or_dir?))
43
-
44
- add_offense(node, location: :expression)
45
- end
46
-
47
-
48
- def_node_search :force_encoding, <<-PATTERN
49
- (send ({lvar ivar cvar} $_) :force_encoding ...)
50
- PATTERN
51
-
52
- def on_assign(node)
53
- lhs, value = *node
54
- return unless magic_file_or_dir?(value)
55
- # After assigning __FILE__ or __dir_ to a variable, check the parent
56
- # scope to whether .force_encoding is called on the variable.
57
- return if node.parent.nil?
58
-
59
- encoded = force_encoding(node.parent).to_a
60
- return if encoded.include?(lhs)
61
-
62
- add_offense(node)
63
- end
64
-
65
- alias on_lvasgn on_assign
66
- alias on_masgn on_assign
67
- alias on_casgn on_assign
68
- alias on_ivasgn on_assign
69
- alias on_cvasgn on_assign
70
- alias on_gvasgn on_assign
71
- alias on_or_asgn on_assign
72
- alias on_and_asgn on_assign
73
- alias on_op_asgn on_assign
74
-
75
- end
76
- end
77
- end
78
- end
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module SketchupSuggestions
6
+ # When using __FILE__ and __dir__, beware that Ruby doesn't apply the
7
+ # correct encoding to the strings under Windows. When they contain
8
+ # non-english characters it will lead to exceptions being raised when the
9
+ # strings are used. Force encoding to work around this.
10
+ #
11
+ # @example Might fail
12
+ # basename = File.basename(__FILE__, '.*')
13
+ #
14
+ # @example Workaround
15
+ # file = __FILE__.dup
16
+ # file.force_encoding('UTF-8') if file.respond_to?(:force_encoding)
17
+ # basename = File.basename(file, '.*')
18
+ class FileEncoding < SketchUp::Cop
19
+
20
+ MSG = 'Beware encoding bug with `__FILE__` and `__dir__`.'.freeze
21
+
22
+ def_node_matcher :file_loaded?, <<-PATTERN
23
+ (send nil? {:file_loaded? :file_loaded} ...)
24
+ PATTERN
25
+
26
+ def_node_matcher :magic_dir?, <<-PATTERN
27
+ (send nil? :__dir__)
28
+ PATTERN
29
+
30
+ def magic_file?(node)
31
+ node.respond_to?(:str_type?) &&
32
+ node.str_type? &&
33
+ node.source_range.is?('__FILE__')
34
+ end
35
+
36
+ def magic_file_or_dir?(node)
37
+ magic_file?(node) || magic_dir?(node)
38
+ end
39
+
40
+ def on_send(node)
41
+ return if file_loaded?(node)
42
+ return if node.arguments.none?(&method(:magic_file_or_dir?))
43
+
44
+ add_offense(node, location: :expression)
45
+ end
46
+
47
+
48
+ def_node_search :force_encoding, <<-PATTERN
49
+ (send ({lvar ivar cvar} $_) :force_encoding ...)
50
+ PATTERN
51
+
52
+ def on_assign(node)
53
+ lhs, value = *node
54
+ return unless magic_file_or_dir?(value)
55
+ # After assigning __FILE__ or __dir_ to a variable, check the parent
56
+ # scope to whether .force_encoding is called on the variable.
57
+ return if node.parent.nil?
58
+
59
+ encoded = force_encoding(node.parent).to_a
60
+ return if encoded.include?(lhs)
61
+
62
+ add_offense(node)
63
+ end
64
+
65
+ alias on_lvasgn on_assign
66
+ alias on_masgn on_assign
67
+ alias on_casgn on_assign
68
+ alias on_ivasgn on_assign
69
+ alias on_cvasgn on_assign
70
+ alias on_gvasgn on_assign
71
+ alias on_or_asgn on_assign
72
+ alias on_and_asgn on_assign
73
+ alias on_op_asgn on_assign
74
+
75
+ end
76
+ end
77
+ end
78
+ end
@@ -1,58 +1,58 @@
1
- # frozen_string_literal: true
2
-
3
- module RuboCop
4
- module Cop
5
- module SketchupSuggestions
6
- # Prefer `model.active_entities` over `model.entities`.
7
- #
8
- # Most tools/actions act upon the active entities context. This could be
9
- # an opened group or component instance. Because of this, prefer
10
- # `model.active_entities` by default over `model.entities` unless you
11
- # have an explicit reason to work in the root model context.
12
- class ModelEntities < SketchUp::Cop
13
-
14
- MSG = 'Prefer `model.active_entities` over `model.entities`.'.freeze
15
-
16
- # Reference: http://www.rubydoc.info/gems/rubocop/RuboCop/NodePattern
17
- def_node_matcher :active_model_entities?, <<-PATTERN
18
- (send
19
- (send (const nil? :Sketchup) :active_model) :entities)
20
- PATTERN
21
-
22
- def_node_matcher :entities_receiver, <<-PATTERN
23
- (send
24
- ({lvar ivar cvar} $_) :entities)
25
- PATTERN
26
-
27
- MODEL_VARIABLE_NAMES = %w[model mod].freeze
28
-
29
- def model_entities?(node)
30
- return true if active_model_entities?(node)
31
-
32
- name = entities_receiver(node)
33
- name && model_variable?(name)
34
- end
35
-
36
- def on_send(node)
37
- add_offense(node, location: :expression) if model_entities?(node)
38
- end
39
-
40
- private
41
-
42
- def model_variable?(name)
43
- basename = variable_basename(name)
44
- MODEL_VARIABLE_NAMES.include?(basename)
45
- end
46
-
47
- def variable_basename(name)
48
- # Extract the basename from variables:
49
- # model => model
50
- # @model => model
51
- # @@model => model
52
- name.to_s.tr('@', '')
53
- end
54
-
55
- end
56
- end
57
- end
58
- end
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module SketchupSuggestions
6
+ # Prefer `model.active_entities` over `model.entities`.
7
+ #
8
+ # Most tools/actions act upon the active entities context. This could be
9
+ # an opened group or component instance. Because of this, prefer
10
+ # `model.active_entities` by default over `model.entities` unless you
11
+ # have an explicit reason to work in the root model context.
12
+ class ModelEntities < SketchUp::Cop
13
+
14
+ MSG = 'Prefer `model.active_entities` over `model.entities`.'.freeze
15
+
16
+ # Reference: http://www.rubydoc.info/gems/rubocop/RuboCop/NodePattern
17
+ def_node_matcher :active_model_entities?, <<-PATTERN
18
+ (send
19
+ (send (const nil? :Sketchup) :active_model) :entities)
20
+ PATTERN
21
+
22
+ def_node_matcher :entities_receiver, <<-PATTERN
23
+ (send
24
+ ({lvar ivar cvar} $_) :entities)
25
+ PATTERN
26
+
27
+ MODEL_VARIABLE_NAMES = %w[model mod].freeze
28
+
29
+ def model_entities?(node)
30
+ return true if active_model_entities?(node)
31
+
32
+ name = entities_receiver(node)
33
+ name && model_variable?(name)
34
+ end
35
+
36
+ def on_send(node)
37
+ add_offense(node, location: :expression) if model_entities?(node)
38
+ end
39
+
40
+ private
41
+
42
+ def model_variable?(name)
43
+ basename = variable_basename(name)
44
+ MODEL_VARIABLE_NAMES.include?(basename)
45
+ end
46
+
47
+ def variable_basename(name)
48
+ # Extract the basename from variables:
49
+ # model => model
50
+ # @model => model
51
+ # @@model => model
52
+ name.to_s.tr('@', '')
53
+ end
54
+
55
+ end
56
+ end
57
+ end
58
+ end
@@ -1,45 +1,45 @@
1
- # frozen_string_literal: true
2
-
3
- module RuboCop
4
- module Cop
5
- module SketchupSuggestions
6
- # Some of the shipped extensions in SketchUp monkey-patch the API
7
- # namespace. This is an unfortunate no-no that was done a long time ago
8
- # before the extension best-practices were established. These functions
9
- # might change or be removed at any time. They will also not work when
10
- # the extensions are disabled. Avoid using these methods.
11
- class MonkeyPatchedApi < SketchUp::Cop
12
-
13
- include SketchUp::DynamicComponentMethods
14
-
15
- def on_send(node)
16
- # Only check instance methods.
17
- return if node.receiver && node.receiver.const_type?
18
-
19
- name = node.method_name
20
-
21
- dc_method = DC_METHODS.find { |m| m[:name] == name }
22
- return unless dc_method
23
-
24
- if dc_method.key?(:variables)
25
- return unless node.receiver && node.receiver.variable?
26
-
27
- receiver_name = node.receiver.children.first
28
- # Account for instance and class variables.
29
- receiver_name = receiver_name.to_s.tr('@', '').to_sym
30
- return unless dc_method[:variables].include?(receiver_name)
31
- end
32
-
33
- path = dc_method[:path]
34
- message = "#{path}##{name} is not part of the official API. "\
35
- "It's a monkey-patched addition by Dynamic Components."
36
- add_offense(node,
37
- location: :selector,
38
- severity: :warning,
39
- message: message)
40
- end
41
-
42
- end
43
- end
44
- end
45
- end
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module SketchupSuggestions
6
+ # Some of the shipped extensions in SketchUp monkey-patch the API
7
+ # namespace. This is an unfortunate no-no that was done a long time ago
8
+ # before the extension best-practices were established. These functions
9
+ # might change or be removed at any time. They will also not work when
10
+ # the extensions are disabled. Avoid using these methods.
11
+ class MonkeyPatchedApi < SketchUp::Cop
12
+
13
+ include SketchUp::DynamicComponentMethods
14
+
15
+ def on_send(node)
16
+ # Only check instance methods.
17
+ return if node.receiver && node.receiver.const_type?
18
+
19
+ name = node.method_name
20
+
21
+ dc_method = DC_METHODS.find { |m| m[:name] == name }
22
+ return unless dc_method
23
+
24
+ if dc_method.key?(:variables)
25
+ return unless node.receiver && node.receiver.variable?
26
+
27
+ receiver_name = node.receiver.children.first
28
+ # Account for instance and class variables.
29
+ receiver_name = receiver_name.to_s.tr('@', '').to_sym
30
+ return unless dc_method[:variables].include?(receiver_name)
31
+ end
32
+
33
+ path = dc_method[:path]
34
+ message = "#{path}##{name} is not part of the official API. "\
35
+ "It's a monkey-patched addition by Dynamic Components."
36
+ add_offense(node,
37
+ location: :selector,
38
+ severity: :warning,
39
+ message: message)
40
+ end
41
+
42
+ end
43
+ end
44
+ end
45
+ end
@@ -1,103 +1,103 @@
1
- # frozen_string_literal: true
2
-
3
- module RuboCop
4
- module Cop
5
- module SketchupSuggestions
6
- # Operation name should be a short capitalized description. It will be
7
- # visible to the user in the Edit > Undo menu. Make sure to give it a
8
- # short human readable name, similar to SketchUp's own operation names.
9
- #
10
- # This cop make some very naive assumptions and will have more false
11
- # positives than most of the other cops. It's purpose is mainly to enable
12
- # awareness.
13
- class OperationName < SketchUp::Cop
14
-
15
- include RangeHelp
16
-
17
- MSG = 'Operation name should be a short capitalized description.'.freeze
18
- MSG_MAX = 'Operation names should not be short and concise. [%d/%d]'.freeze
19
-
20
- def on_send(node)
21
- _, method_name, *args = *node
22
- return unless method_name == :start_operation
23
- return if args.empty? || !args.first.str_type?
24
-
25
- # Ignore transparent operations.
26
- return if args.size == 4 && args.last.true_type?
27
-
28
- operation_name = args.first.str_content
29
- # We can only inspect string literals.
30
- return unless operation_name.is_a?(String)
31
-
32
- # Check the format of the operation name.
33
- unless acceptable_operation_name?(operation_name)
34
- msg = %(#{MSG} Expected: `"#{titleize(operation_name)}"`)
35
- add_offense(args.first, location: :expression, message: msg)
36
- end
37
- # Check the length of the operation name.
38
- unless operation_name.size <= max_operation_name_length
39
- message = format(MSG_MAX, operation_name.size, max_operation_name_length)
40
- add_offense(args.first,
41
- location: excess_range(args.first, operation_name),
42
- message: message)
43
- end
44
- # Ensure operation name is not empty.
45
- if operation_name.empty?
46
- msg = 'Operation names should not be empty.'
47
- add_offense(args.first, location: :expression, message: msg)
48
- end
49
- end
50
-
51
- private
52
-
53
- def excess_range(node, operation_name)
54
- string_start = node.source.index(operation_name)
55
- range = node.loc.expression
56
- if string_start
57
- excess_start = range.begin_pos + string_start + max_operation_name_length
58
- excess_end = range.begin_pos + string_start + operation_name.size
59
- range_between(excess_start, excess_end)
60
- else
61
- range_between(range.begin_pos, range.end_pos)
62
- end
63
- end
64
-
65
- def acceptable_operation_name?(name)
66
- # Capitalization, no programmer name, no punctuation.
67
- return false if name.end_with?('.')
68
- return false if titleize(name) != name
69
-
70
- true
71
- end
72
-
73
- def max_operation_name_length
74
- length = cop_config['Max'] || 25
75
- return length if length.is_a?(Integer) && length > 0
76
-
77
- raise 'Max needs to be a positive integer!'
78
- end
79
-
80
- TITLEIZE_EXCLUDE = %w[
81
- by for from in of to
82
- and or if
83
- ].freeze
84
-
85
- def titleize(string)
86
- string = string.gsub(/[_.]/, ' ')
87
- words = string.split.map { |word|
88
- unless TITLEIZE_EXCLUDE.include?(word)
89
- # word.capitalize won't work here, as we want to allow words like:
90
- # "HTML", "SketchUp". So instead only the first character in each
91
- # word is modified.
92
- char = word[0].upcase
93
- word[0, 1] = char
94
- end
95
- word
96
- }
97
- words.join(' ')
98
- end
99
-
100
- end
101
- end
102
- end
103
- end
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module SketchupSuggestions
6
+ # Operation name should be a short capitalized description. It will be
7
+ # visible to the user in the Edit > Undo menu. Make sure to give it a
8
+ # short human readable name, similar to SketchUp's own operation names.
9
+ #
10
+ # This cop make some very naive assumptions and will have more false
11
+ # positives than most of the other cops. It's purpose is mainly to enable
12
+ # awareness.
13
+ class OperationName < SketchUp::Cop
14
+
15
+ include RangeHelp
16
+
17
+ MSG = 'Operation name should be a short capitalized description.'.freeze
18
+ MSG_MAX = 'Operation names should not be short and concise. [%d/%d]'.freeze
19
+
20
+ def on_send(node)
21
+ _, method_name, *args = *node
22
+ return unless method_name == :start_operation
23
+ return if args.empty? || !args.first.str_type?
24
+
25
+ # Ignore transparent operations.
26
+ return if args.size == 4 && args.last.true_type?
27
+
28
+ operation_name = args.first.str_content
29
+ # We can only inspect string literals.
30
+ return unless operation_name.is_a?(String)
31
+
32
+ # Check the format of the operation name.
33
+ unless acceptable_operation_name?(operation_name)
34
+ msg = %(#{MSG} Expected: `"#{titleize(operation_name)}"`)
35
+ add_offense(args.first, location: :expression, message: msg)
36
+ end
37
+ # Check the length of the operation name.
38
+ unless operation_name.size <= max_operation_name_length
39
+ message = format(MSG_MAX, operation_name.size, max_operation_name_length)
40
+ add_offense(args.first,
41
+ location: excess_range(args.first, operation_name),
42
+ message: message)
43
+ end
44
+ # Ensure operation name is not empty.
45
+ if operation_name.empty?
46
+ msg = 'Operation names should not be empty.'
47
+ add_offense(args.first, location: :expression, message: msg)
48
+ end
49
+ end
50
+
51
+ private
52
+
53
+ def excess_range(node, operation_name)
54
+ string_start = node.source.index(operation_name)
55
+ range = node.loc.expression
56
+ if string_start
57
+ excess_start = range.begin_pos + string_start + max_operation_name_length
58
+ excess_end = range.begin_pos + string_start + operation_name.size
59
+ range_between(excess_start, excess_end)
60
+ else
61
+ range_between(range.begin_pos, range.end_pos)
62
+ end
63
+ end
64
+
65
+ def acceptable_operation_name?(name)
66
+ # Capitalization, no programmer name, no punctuation.
67
+ return false if name.end_with?('.')
68
+ return false if titleize(name) != name
69
+
70
+ true
71
+ end
72
+
73
+ def max_operation_name_length
74
+ length = cop_config['Max'] || 25
75
+ return length if length.is_a?(Integer) && length > 0
76
+
77
+ raise 'Max needs to be a positive integer!'
78
+ end
79
+
80
+ TITLEIZE_EXCLUDE = %w[
81
+ by for from in of to
82
+ and or if
83
+ ].freeze
84
+
85
+ def titleize(string)
86
+ string = string.gsub(/[_.]/, ' ')
87
+ words = string.split.map { |word|
88
+ unless TITLEIZE_EXCLUDE.include?(word)
89
+ # word.capitalize won't work here, as we want to allow words like:
90
+ # "HTML", "SketchUp". So instead only the first character in each
91
+ # word is modified.
92
+ char = word[0].upcase
93
+ word[0, 1] = char
94
+ end
95
+ word
96
+ }
97
+ words.join(' ')
98
+ end
99
+
100
+ end
101
+ end
102
+ end
103
+ end