rubocop-sketchup 0.5.0 → 0.6.0

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