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,163 +1,163 @@
1
- # frozen_string_literal: true
2
-
3
- module RuboCop
4
- module Cop
5
- module SketchupRequirements
6
- # Omit file extensions when using `Sketchup.require` to allow encrypted
7
- # files to be loaded.
8
- #
9
- # Ruby C extensions, `.so`/`.bundle` libraries must always be loaded via
10
- # the normal `require`. Omit the file extension to the libraries as Ruby
11
- # will resolve the`.so`/`.bundle` automatically.
12
- #
13
- # @example Bad - This will fail if extension is encrypted
14
- # Sketchup.require 'hello/world.rb'
15
- #
16
- # @example Good - This will work for `.rbe`, `.rbs` and `rb` files.
17
- # Sketchup.require 'hello/world'
18
- #
19
- # @example Bad - This will fail if extension is encrypted
20
- # extension = SketchupExtension.new("Example", "Example/main.rb")
21
- #
22
- # @example Good - This will work for `.rbe`, `.rbs` and `rb` files.
23
- # extension = SketchupExtension.new("Example", "Example/main")
24
- class SketchupRequire < SketchUp::Cop
25
-
26
- include SketchUp::ExtensionProject
27
- include SketchUp::NoCommentDisable
28
- include RangeHelp
29
-
30
- MSG_SKETCHUP_REQUIRE_EXT_NAME = 'Do not hard code file extensions '\
31
- 'with `Sketchup.require`.'.freeze
32
-
33
- MSG_EXTENSION_NEW_EXT_NAME = 'Do not hard code file extensions '\
34
- 'with `SketchupExtension.new`.'.freeze
35
-
36
- MSG_REQUIRE_FOR_BINARY = 'Use `require` instead of `Sketchup.require` '\
37
- 'to load binary Ruby libraries.'.freeze
38
-
39
- MSG_OMIT_BINARY_EXT = 'Do not hard code .so/.bundle file '\
40
- 'extensions'.freeze
41
-
42
- MSG_REQUIRE_ENCRYPTED = 'Use `Sketchup.require` when loading Ruby '\
43
- 'files for encrypted extensions.'.freeze
44
-
45
- def_node_matcher :ruby_require, <<-PATTERN
46
- (send nil? :require (str $_))
47
- PATTERN
48
-
49
- def_node_matcher :ruby_require?, <<-PATTERN
50
- (send nil? :require (str _))
51
- PATTERN
52
-
53
-
54
- def_node_matcher :sketchup_require, <<-PATTERN
55
- (send (const nil? :Sketchup) :require (str $_))
56
- PATTERN
57
-
58
- def_node_matcher :sketchup_require?, <<-PATTERN
59
- (send (const nil? :Sketchup) :require (str _))
60
- PATTERN
61
-
62
-
63
- def_node_matcher :sketchup_extension_new, <<-PATTERN
64
- (send (const nil? :SketchupExtension) :new _ (str $_))
65
- PATTERN
66
-
67
- def_node_matcher :sketchup_extension_new?, <<-PATTERN
68
- (send (const nil? :SketchupExtension) :new _ (str _))
69
- PATTERN
70
-
71
-
72
- TOOLS_RUBY_FILES = %w[extensions.rb langhandler.rb sketchup.rb].freeze
73
-
74
-
75
- def on_send(node)
76
- if sketchup_require?(node)
77
- filename = sketchup_require(node)
78
- return if check_binary_sketchup_require(node, filename)
79
- return if check_sketchup_require_filename(node, filename)
80
-
81
- elsif ruby_require?(node)
82
- filename = ruby_require(node)
83
- return if check_binary_ruby_require(node, filename)
84
- return if check_encrypted_require(node, filename)
85
-
86
- elsif sketchup_extension_new?(node)
87
- filename = sketchup_extension_new(node)
88
- return if check_sketchup_extension_new_filename(node, filename)
89
-
90
- end
91
- end
92
-
93
- private
94
-
95
- def binary_require?(filename)
96
- return unless extension_binaries?
97
- return if extension_binaries.empty?
98
-
99
- extension_binaries.include?(filename)
100
- end
101
-
102
- def check_binary_sketchup_require(node, filename)
103
- return unless binary_require?(filename)
104
-
105
- end_pos = node.loc.dot.end_pos
106
- range = node.receiver.loc.expression.with(end_pos: end_pos)
107
- add_offense(node, location: range, message: MSG_REQUIRE_FOR_BINARY)
108
- true
109
- end
110
-
111
- def check_binary_ruby_require(node, filename)
112
- ext_name = File.extname(filename)
113
- return unless %w[.so .bundle].include?(ext_name)
114
-
115
- add_offense(node, location: file_ext_range(node.arguments.first),
116
- message: MSG_OMIT_BINARY_EXT)
117
- true
118
- end
119
-
120
- def check_sketchup_require_filename(node, filename)
121
- return if valid_filename?(filename)
122
-
123
- add_offense(node, location: file_ext_range(node.arguments.first),
124
- message: MSG_SKETCHUP_REQUIRE_EXT_NAME)
125
- true
126
- end
127
-
128
- def check_sketchup_extension_new_filename(node, filename)
129
- return if valid_filename?(filename)
130
-
131
- add_offense(node, location: file_ext_range(node.arguments.last),
132
- message: MSG_EXTENSION_NEW_EXT_NAME)
133
- true
134
- end
135
-
136
- def check_encrypted_require(node, filename)
137
- return unless encrypted_extension?
138
- return unless extension_file?(filename)
139
-
140
- add_offense(node, location: node.loc.selector,
141
- message: MSG_REQUIRE_ENCRYPTED)
142
- true
143
- end
144
-
145
- def first_directory(path)
146
- path.to_s.split(File::SEPARATOR).first.downcase
147
- end
148
-
149
- def extension_file?(filename)
150
- pathname = Pathname.new(filename).cleanpath
151
- return false unless pathname.relative?
152
-
153
- first_directory(extension_directory) == first_directory(pathname)
154
- end
155
-
156
- def valid_filename?(filename)
157
- File.extname(filename).empty? || TOOLS_RUBY_FILES.include?(filename)
158
- end
159
-
160
- end
161
- end
162
- end
163
- end
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module SketchupRequirements
6
+ # Omit file extensions when using `Sketchup.require` to allow encrypted
7
+ # files to be loaded.
8
+ #
9
+ # Ruby C extensions, `.so`/`.bundle` libraries must always be loaded via
10
+ # the normal `require`. Omit the file extension to the libraries as Ruby
11
+ # will resolve the`.so`/`.bundle` automatically.
12
+ #
13
+ # @example Bad - This will fail if extension is encrypted
14
+ # Sketchup.require 'hello/world.rb'
15
+ #
16
+ # @example Good - This will work for `.rbe`, `.rbs` and `rb` files.
17
+ # Sketchup.require 'hello/world'
18
+ #
19
+ # @example Bad - This will fail if extension is encrypted
20
+ # extension = SketchupExtension.new("Example", "Example/main.rb")
21
+ #
22
+ # @example Good - This will work for `.rbe`, `.rbs` and `rb` files.
23
+ # extension = SketchupExtension.new("Example", "Example/main")
24
+ class SketchupRequire < SketchUp::Cop
25
+
26
+ include SketchUp::ExtensionProject
27
+ include SketchUp::NoCommentDisable
28
+ include RangeHelp
29
+
30
+ MSG_SKETCHUP_REQUIRE_EXT_NAME = 'Do not hard code file extensions '\
31
+ 'with `Sketchup.require`.'.freeze
32
+
33
+ MSG_EXTENSION_NEW_EXT_NAME = 'Do not hard code file extensions '\
34
+ 'with `SketchupExtension.new`.'.freeze
35
+
36
+ MSG_REQUIRE_FOR_BINARY = 'Use `require` instead of `Sketchup.require` '\
37
+ 'to load binary Ruby libraries.'.freeze
38
+
39
+ MSG_OMIT_BINARY_EXT = 'Do not hard code .so/.bundle file '\
40
+ 'extensions'.freeze
41
+
42
+ MSG_REQUIRE_ENCRYPTED = 'Use `Sketchup.require` when loading Ruby '\
43
+ 'files for encrypted extensions.'.freeze
44
+
45
+ def_node_matcher :ruby_require, <<-PATTERN
46
+ (send nil? :require (str $_))
47
+ PATTERN
48
+
49
+ def_node_matcher :ruby_require?, <<-PATTERN
50
+ (send nil? :require (str _))
51
+ PATTERN
52
+
53
+
54
+ def_node_matcher :sketchup_require, <<-PATTERN
55
+ (send (const nil? :Sketchup) :require (str $_))
56
+ PATTERN
57
+
58
+ def_node_matcher :sketchup_require?, <<-PATTERN
59
+ (send (const nil? :Sketchup) :require (str _))
60
+ PATTERN
61
+
62
+
63
+ def_node_matcher :sketchup_extension_new, <<-PATTERN
64
+ (send (const nil? :SketchupExtension) :new _ (str $_))
65
+ PATTERN
66
+
67
+ def_node_matcher :sketchup_extension_new?, <<-PATTERN
68
+ (send (const nil? :SketchupExtension) :new _ (str _))
69
+ PATTERN
70
+
71
+
72
+ TOOLS_RUBY_FILES = %w[extensions.rb langhandler.rb sketchup.rb].freeze
73
+
74
+
75
+ def on_send(node)
76
+ if sketchup_require?(node)
77
+ filename = sketchup_require(node)
78
+ return if check_binary_sketchup_require(node, filename)
79
+ return if check_sketchup_require_filename(node, filename)
80
+
81
+ elsif ruby_require?(node)
82
+ filename = ruby_require(node)
83
+ return if check_binary_ruby_require(node, filename)
84
+ return if check_encrypted_require(node, filename)
85
+
86
+ elsif sketchup_extension_new?(node)
87
+ filename = sketchup_extension_new(node)
88
+ return if check_sketchup_extension_new_filename(node, filename)
89
+
90
+ end
91
+ end
92
+
93
+ private
94
+
95
+ def binary_require?(filename)
96
+ return unless extension_binaries?
97
+ return if extension_binaries.empty?
98
+
99
+ extension_binaries.include?(filename)
100
+ end
101
+
102
+ def check_binary_sketchup_require(node, filename)
103
+ return unless binary_require?(filename)
104
+
105
+ end_pos = node.loc.dot.end_pos
106
+ range = node.receiver.loc.expression.with(end_pos: end_pos)
107
+ add_offense(node, location: range, message: MSG_REQUIRE_FOR_BINARY)
108
+ true
109
+ end
110
+
111
+ def check_binary_ruby_require(node, filename)
112
+ ext_name = File.extname(filename)
113
+ return unless %w[.so .bundle].include?(ext_name)
114
+
115
+ add_offense(node, location: file_ext_range(node.arguments.first),
116
+ message: MSG_OMIT_BINARY_EXT)
117
+ true
118
+ end
119
+
120
+ def check_sketchup_require_filename(node, filename)
121
+ return if valid_filename?(filename)
122
+
123
+ add_offense(node, location: file_ext_range(node.arguments.first),
124
+ message: MSG_SKETCHUP_REQUIRE_EXT_NAME)
125
+ true
126
+ end
127
+
128
+ def check_sketchup_extension_new_filename(node, filename)
129
+ return if valid_filename?(filename)
130
+
131
+ add_offense(node, location: file_ext_range(node.arguments.last),
132
+ message: MSG_EXTENSION_NEW_EXT_NAME)
133
+ true
134
+ end
135
+
136
+ def check_encrypted_require(node, filename)
137
+ return unless encrypted_extension?
138
+ return unless extension_file?(filename)
139
+
140
+ add_offense(node, location: node.loc.selector,
141
+ message: MSG_REQUIRE_ENCRYPTED)
142
+ true
143
+ end
144
+
145
+ def first_directory(path)
146
+ path.to_s.split(File::SEPARATOR).first.downcase
147
+ end
148
+
149
+ def extension_file?(filename)
150
+ pathname = Pathname.new(filename).cleanpath
151
+ return false unless pathname.relative?
152
+
153
+ first_directory(extension_directory) == first_directory(pathname)
154
+ end
155
+
156
+ def valid_filename?(filename)
157
+ File.extname(filename).empty? || TOOLS_RUBY_FILES.include?(filename)
158
+ end
159
+
160
+ end
161
+ end
162
+ end
163
+ end
@@ -1,49 +1,49 @@
1
- # frozen_string_literal: true
2
-
3
- module RuboCop
4
- module Cop
5
- module SketchupSuggestions
6
- # The idiomatic way to create groups via the Ruby API differ from the
7
- # way you'd do it from the UI.
8
- #
9
- # Using the API you should prefer to create the group first, then add
10
- # your geometry into the group. This is more performant and predictable.
11
- #
12
- # Grouping existing geometry via the API have historically been affected
13
- # by bugs and issues.
14
- #
15
- # If you do have to group existing geometry via the API, make sure you
16
- # group geometry from the active context; `model.active_entities`.
17
- # Otherwise you might run into unexpected issues, even crashes.
18
- #
19
- # @example Adding new geometry
20
- # # bad
21
- # face1 = model.active_entities.add_face(points1)
22
- # face2 = model.active_entities.add_face(points2)
23
- # group = model.active_entities.add_group([face1, face2])
24
- #
25
- # # good
26
- # group = model.active_entities.add_group
27
- # face1 = group.entities.add_face(points1)
28
- # face2 = group.entities.add_face(points2)
29
- class AddGroup < Cop
30
-
31
- include RangeHelp
32
-
33
- MSG = 'Avoid creating groups out of existing entities.'.freeze
34
-
35
- def_node_matcher :add_group?, <<-PATTERN
36
- (send _ :add_group ...)
37
- PATTERN
38
-
39
- def on_send(node)
40
- return unless add_group?(node)
41
- return if node.arguments.empty?
42
-
43
- add_offense(node, location: arguments_range(node))
44
- end
45
-
46
- end
47
- end
48
- end
49
- end
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module SketchupSuggestions
6
+ # The idiomatic way to create groups via the Ruby API differ from the
7
+ # way you'd do it from the UI.
8
+ #
9
+ # Using the API you should prefer to create the group first, then add
10
+ # your geometry into the group. This is more performant and predictable.
11
+ #
12
+ # Grouping existing geometry via the API have historically been affected
13
+ # by bugs and issues.
14
+ #
15
+ # If you do have to group existing geometry via the API, make sure you
16
+ # group geometry from the active context; `model.active_entities`.
17
+ # Otherwise you might run into unexpected issues, even crashes.
18
+ #
19
+ # @example Adding new geometry
20
+ # # bad
21
+ # face1 = model.active_entities.add_face(points1)
22
+ # face2 = model.active_entities.add_face(points2)
23
+ # group = model.active_entities.add_group([face1, face2])
24
+ #
25
+ # # good
26
+ # group = model.active_entities.add_group
27
+ # face1 = group.entities.add_face(points1)
28
+ # face2 = group.entities.add_face(points2)
29
+ class AddGroup < Cop
30
+
31
+ include RangeHelp
32
+
33
+ MSG = 'Avoid creating groups out of existing entities.'.freeze
34
+
35
+ def_node_matcher :add_group?, <<-PATTERN
36
+ (send _ :add_group ...)
37
+ PATTERN
38
+
39
+ def on_send(node)
40
+ return unless add_group?(node)
41
+ return if node.arguments.empty?
42
+
43
+ add_offense(node, location: arguments_range(node))
44
+ end
45
+
46
+ end
47
+ end
48
+ end
49
+ end
@@ -1,117 +1,117 @@
1
- # frozen_string_literal: true
2
-
3
- module RuboCop
4
- module Cop
5
- module SketchupSuggestions
6
- # It's easy to lose track of what API feature was added in what version or
7
- # SketchUp. You can configure your target SketchUp version and be notified
8
- # if you use features introduced in newer versions.
9
- #
10
- # @example Add this to your .rubocop.yml
11
- # AllCops:
12
- # SketchUp:
13
- # TargetSketchUpVersion: 2016 M1
14
- class Compatibility < SketchUp::Cop
15
-
16
- include SketchUp::Features
17
-
18
- MSG = 'Incompatible feature with target SketchUp version'.freeze
19
-
20
- def on_def(node)
21
- return unless observer_method?(node)
22
-
23
- feature_name = "##{node.method_name}"
24
- check_feature(node, :method, feature_name)
25
- end
26
-
27
- def on_send(node)
28
- feature_name = ''
29
- if module_method?(node)
30
- feature_name = "#{node.receiver.const_name}.#{node.method_name}"
31
- else
32
- # Instance methods are harder. It's difficult to infer the type of
33
- # the receiver. If we only check the method name in isolation we
34
- # will get too many false positives with method names matching
35
- # methods in Ruby itself and other older features.
36
- # We try to match names that are unlikely to cause much noise.
37
- return unless checkable_instance_method?(node)
38
-
39
- feature_name = "##{node.method_name}"
40
- end
41
- check_feature(node, :method, feature_name)
42
- end
43
-
44
- def on_const(node)
45
- feature_name = node.const_name
46
- [:class, :module, :constant].each { |type|
47
- check_feature(node, type, feature_name)
48
- }
49
- end
50
-
51
- private
52
-
53
- def check_feature(node, type, feature_name)
54
- return unless sketchup_target_version?
55
-
56
- full_feature_name = feature_name
57
- FEATURES.each { |feature_set|
58
- version = feature_set[:version]
59
- feature_version = SketchUp::SketchUpVersion.new(version)
60
- next unless feature_version > sketchup_target_version
61
-
62
- objects = feature_set[:types][type] || []
63
- if type == :method && instance_method?(feature_name)
64
- # Instance methods are simply matching the method name since it's
65
- # very difficult to determine the type of the receiver.
66
- full_feature_name = objects.find { |object|
67
- object.end_with?(feature_name)
68
- }
69
- next unless full_feature_name
70
- else
71
- next unless objects.include?(feature_name)
72
- end
73
- report(node, full_feature_name, feature_version, type)
74
- }
75
- end
76
-
77
- def report(node, feature_name, feature_version, feature_type)
78
- message = "The #{feature_type} `#{feature_name}` was added in "\
79
- "#{feature_version} which is incompatible with target "\
80
- "#{sketchup_target_version}."
81
- location = find_node_location(node)
82
- add_offense(node, location: location, message: message)
83
- end
84
-
85
- def find_node_location(node)
86
- # Highlight the most pertinent piece of the expression.
87
- if node.const_type?
88
- :expression
89
- elsif node.send_type?
90
- :selector
91
- elsif node.def_type?
92
- :name
93
- else
94
- :expression
95
- end
96
- end
97
-
98
- def module_method?(node)
99
- node.receiver && node.receiver.const_type?
100
- end
101
-
102
- def instance_method?(feature_name)
103
- feature_name.start_with?('#')
104
- end
105
-
106
- def checkable_instance_method?(node)
107
- INSTANCE_METHODS.include?(node.method_name)
108
- end
109
-
110
- def observer_method?(node)
111
- OBSERVER_METHODS.include?(node.method_name)
112
- end
113
-
114
- end
115
- end
116
- end
117
- end
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module SketchupSuggestions
6
+ # It's easy to lose track of what API feature was added in what version or
7
+ # SketchUp. You can configure your target SketchUp version and be notified
8
+ # if you use features introduced in newer versions.
9
+ #
10
+ # @example Add this to your .rubocop.yml
11
+ # AllCops:
12
+ # SketchUp:
13
+ # TargetSketchUpVersion: 2016 M1
14
+ class Compatibility < SketchUp::Cop
15
+
16
+ include SketchUp::Features
17
+
18
+ MSG = 'Incompatible feature with target SketchUp version'.freeze
19
+
20
+ def on_def(node)
21
+ return unless observer_method?(node)
22
+
23
+ feature_name = "##{node.method_name}"
24
+ check_feature(node, :method, feature_name)
25
+ end
26
+
27
+ def on_send(node)
28
+ feature_name = ''
29
+ if module_method?(node)
30
+ feature_name = "#{node.receiver.const_name}.#{node.method_name}"
31
+ else
32
+ # Instance methods are harder. It's difficult to infer the type of
33
+ # the receiver. If we only check the method name in isolation we
34
+ # will get too many false positives with method names matching
35
+ # methods in Ruby itself and other older features.
36
+ # We try to match names that are unlikely to cause much noise.
37
+ return unless checkable_instance_method?(node)
38
+
39
+ feature_name = "##{node.method_name}"
40
+ end
41
+ check_feature(node, :method, feature_name)
42
+ end
43
+
44
+ def on_const(node)
45
+ feature_name = node.const_name
46
+ [:class, :module, :constant].each { |type|
47
+ check_feature(node, type, feature_name)
48
+ }
49
+ end
50
+
51
+ private
52
+
53
+ def check_feature(node, type, feature_name)
54
+ return unless sketchup_target_version?
55
+
56
+ full_feature_name = feature_name
57
+ FEATURES.each { |feature_set|
58
+ version = feature_set[:version]
59
+ feature_version = SketchUp::SketchUpVersion.new(version)
60
+ next unless feature_version > sketchup_target_version
61
+
62
+ objects = feature_set[:types][type] || []
63
+ if type == :method && instance_method?(feature_name)
64
+ # Instance methods are simply matching the method name since it's
65
+ # very difficult to determine the type of the receiver.
66
+ full_feature_name = objects.find { |object|
67
+ object.end_with?(feature_name)
68
+ }
69
+ next unless full_feature_name
70
+ else
71
+ next unless objects.include?(feature_name)
72
+ end
73
+ report(node, full_feature_name, feature_version, type)
74
+ }
75
+ end
76
+
77
+ def report(node, feature_name, feature_version, feature_type)
78
+ message = "The #{feature_type} `#{feature_name}` was added in "\
79
+ "#{feature_version} which is incompatible with target "\
80
+ "#{sketchup_target_version}."
81
+ location = find_node_location(node)
82
+ add_offense(node, location: location, message: message)
83
+ end
84
+
85
+ def find_node_location(node)
86
+ # Highlight the most pertinent piece of the expression.
87
+ if node.const_type?
88
+ :expression
89
+ elsif node.send_type?
90
+ :selector
91
+ elsif node.def_type?
92
+ :name
93
+ else
94
+ :expression
95
+ end
96
+ end
97
+
98
+ def module_method?(node)
99
+ node.receiver && node.receiver.const_type?
100
+ end
101
+
102
+ def instance_method?(feature_name)
103
+ feature_name.start_with?('#')
104
+ end
105
+
106
+ def checkable_instance_method?(node)
107
+ INSTANCE_METHODS.include?(node.method_name)
108
+ end
109
+
110
+ def observer_method?(node)
111
+ OBSERVER_METHODS.include?(node.method_name)
112
+ end
113
+
114
+ end
115
+ end
116
+ end
117
+ end