rubocop-sketchup 0.1.5 → 0.2.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 (36) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +6 -2
  3. data/config/default.yml +12 -2
  4. data/lib/rubocop-sketchup.rb +18 -0
  5. data/lib/rubocop/sketchup/config.rb +31 -0
  6. data/lib/rubocop/sketchup/dc_globals.rb +24 -0
  7. data/lib/rubocop/sketchup/extension_project.rb +34 -34
  8. data/lib/rubocop/sketchup/formatter/extension_review.rb +239 -0
  9. data/lib/rubocop/sketchup/inject.rb +1 -1
  10. data/lib/rubocop/sketchup/namespace.rb +38 -40
  11. data/lib/rubocop/sketchup/namespace_checker.rb +35 -37
  12. data/lib/rubocop/sketchup/no_comment_disable.rb +1 -1
  13. data/lib/rubocop/sketchup/performance/typename.rb +1 -1
  14. data/lib/rubocop/sketchup/requirements/api_namespace.rb +2 -2
  15. data/lib/rubocop/sketchup/requirements/exit.rb +2 -2
  16. data/lib/rubocop/sketchup/requirements/extension_namespace.rb +2 -2
  17. data/lib/rubocop/sketchup/requirements/file_structure.rb +14 -5
  18. data/lib/rubocop/sketchup/requirements/global_constants.rb +1 -1
  19. data/lib/rubocop/sketchup/requirements/global_methods.rb +1 -1
  20. data/lib/rubocop/sketchup/requirements/global_variables.rb +19 -7
  21. data/lib/rubocop/sketchup/requirements/language_handler_globals.rb +2 -0
  22. data/lib/rubocop/sketchup/requirements/load_path.rb +1 -1
  23. data/lib/rubocop/sketchup/requirements/minimal_registration.rb +3 -3
  24. data/lib/rubocop/sketchup/requirements/register_extension.rb +3 -3
  25. data/lib/rubocop/sketchup/requirements/require_tools_ruby_files.rb +74 -0
  26. data/lib/rubocop/sketchup/requirements/ruby_core_namespace.rb +2 -2
  27. data/lib/rubocop/sketchup/requirements/ruby_stdlib_namespace.rb +2 -2
  28. data/lib/rubocop/sketchup/requirements/shipped_extensions_namespace.rb +2 -2
  29. data/lib/rubocop/sketchup/requirements/sketchup_extension.rb +18 -6
  30. data/lib/rubocop/sketchup/suggestions/dc_internals.rb +2 -13
  31. data/lib/rubocop/sketchup/suggestions/file_encoding.rb +1 -1
  32. data/lib/rubocop/sketchup/suggestions/model_entities.rb +1 -0
  33. data/lib/rubocop/sketchup/suggestions/operation_name.rb +1 -1
  34. data/lib/rubocop/sketchup/version.rb +1 -1
  35. data/rubocop-sketchup.gemspec +4 -2
  36. metadata +10 -6
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 6a682a986a80b22c8a6a1a4882e7ccf730306dc2
4
- data.tar.gz: f883dfc7ba5d0100b447ac77364078a475c2a770
3
+ metadata.gz: 4d7b803688f7c17a0b11ab8b6ac5452d1a4fa925
4
+ data.tar.gz: 3b0c7b2667761ea2d5a298f9906aeb5c0dd90c93
5
5
  SHA512:
6
- metadata.gz: e763bf2331404f463e0d9d46ff19fadd9d642066eaf8b0606e108f089af006670e7fce3d3efb3eaa1abf14870e6bae732e353aa88fdd17632c0fff0f1364ac92
7
- data.tar.gz: e550faed872bb4308a3658e42cb08aa2bc4880787ca6b01aca53fea0bebfa5291411ee6052ed8feb62466a20c00f66de21567f80b3e0736ee4290fa62e487812
6
+ metadata.gz: bb8900616392157c20a242834e96163a94aa73a98179679e518b4882297b9875916431383e90ff2883fe109de663783b78d6d274f6f3be338575c2cf83986396
7
+ data.tar.gz: 0ac20236f7f5e6046dd072ae3722861f3e008709fa9e4e39b5b9f93036124710718c8bfb170df2c8c5a73ecad9f949dc6b7efcbf455214eee1dc4d3138f3c880
data/Gemfile CHANGED
@@ -3,6 +3,10 @@ source 'https://rubygems.org'
3
3
  gemspec
4
4
 
5
5
  group :test do
6
- gem 'rake', '~> 10.0', require: false
7
- gem 'rspec', '~> 3.4', require: false
6
+ gem 'rake', '~> 12.0', require: false
7
+ gem 'rspec', '~> 3.7', require: false
8
+ end
9
+
10
+ group :development do
11
+ gem 'nokogiri', '~> 1.8', require: false
8
12
  end
data/config/default.yml CHANGED
@@ -1,4 +1,8 @@
1
1
  ---
2
+ AllCops:
3
+ SketchUp:
4
+ SourcePath: src
5
+
2
6
  SketchupDeprecations/OperationNextTransparent:
3
7
  Description: Third argument in `model.start_operation` is deprecated.
4
8
  Details: >-
@@ -60,7 +64,6 @@ SketchupRequirements/FileStructure:
60
64
  Check that the extension conform to expected file structure with a
61
65
  single root .rb file and a support folder with matching name.
62
66
  Enabled: true
63
- SourcePath: src
64
67
 
65
68
  SketchupRequirements/GlobalConstants:
66
69
  Description: Do not introduce global constants.
@@ -102,6 +105,14 @@ SketchupRequirements/RegisterExtension:
102
105
  might confuse users to think the extension isn't working.
103
106
  Enabled: true
104
107
 
108
+ SketchupRequirements/RequireToolsRubyFiles:
109
+ Description: >-
110
+ Due to how require and Sketchup.require checks whether a file
111
+ has been loaded, files from SketchUp's Tools folder should be
112
+ required in using `require` and with their file extension to
113
+ avoid duplicate loading.
114
+ Enabled: true
115
+
105
116
  SketchupRequirements/RubyCoreNamespace:
106
117
  Description: Do not modify Ruby core functionality.
107
118
  Enabled: true
@@ -120,7 +131,6 @@ SketchupRequirements/SketchupExtension:
120
131
  This should be done by the root .rb file in the extension
121
132
  package.
122
133
  Enabled: true
123
- SourcePath: src
124
134
 
125
135
 
126
136
  SketchupSuggestions/DynamicComponentInternals:
@@ -3,6 +3,9 @@ require 'rubocop/sketchup'
3
3
  require 'rubocop/sketchup/version'
4
4
  require 'rubocop/sketchup/inject'
5
5
 
6
+ require 'rubocop/sketchup/formatter/extension_review'
7
+ require 'rubocop/sketchup/config'
8
+ require 'rubocop/sketchup/dc_globals'
6
9
  require 'rubocop/sketchup/extension_project'
7
10
  require 'rubocop/sketchup/namespace'
8
11
  require 'rubocop/sketchup/namespace_checker'
@@ -10,6 +13,21 @@ require 'rubocop/sketchup/no_comment_disable'
10
13
 
11
14
  RuboCop::SketchUp::Inject.defaults!
12
15
 
16
+ # Monkey patching the built in formatter list to add a short alias for custom
17
+ # formatters. Naughty! Naughty!
18
+ class RuboCop::Formatter::FormatterSet
19
+ formatters = BUILTIN_FORMATTERS_FOR_KEYS.dup
20
+ formatters['extension_review'] =
21
+ RuboCop::Formatter::ExtensionReviewFormatter
22
+ verbose = $VERBOSE
23
+ begin
24
+ $VERBOSE = nil
25
+ BUILTIN_FORMATTERS_FOR_KEYS = formatters.freeze
26
+ ensure
27
+ $VERBOSE = verbose
28
+ end
29
+ end
30
+
13
31
  # Load all custom cops.
14
32
  pattern = File.join(__dir__, 'rubocop', 'sketchup', '**/*rb')
15
33
  Dir.glob(pattern) { |file|
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module SketchUp
5
+ module Config
6
+
7
+ DEFAULT_CONFIGURATION =
8
+ RuboCop::SketchUp::CONFIG.fetch('AllCops').fetch('SketchUp')
9
+
10
+ private
11
+
12
+ def all_cops_config
13
+ config.for_all_cops
14
+ end
15
+
16
+ def sketchup_cops_config
17
+ config.for_all_cops.fetch('SketchUp', DEFAULT_CONFIGURATION)
18
+ end
19
+
20
+ def sketchup_source_path_config?
21
+ return unless all_cops_config.key?('SketchUp')
22
+ all_cops_config.fetch('SketchUp').key?('SourcePath')
23
+ end
24
+
25
+ def sketchup_source_path_config
26
+ sketchup_cops_config.fetch('SourcePath')
27
+ end
28
+
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module SketchUp
5
+ module DynamicComponentGlobals
6
+
7
+ DC_GLOBALS = %i[
8
+ $dc_strings
9
+ $dc_extension
10
+ $dc_CONFIGURATOR_NAME
11
+ $dc_REPORTER_NAME
12
+ $dc_MANAGER_NAME
13
+ $dc_observers
14
+ ]
15
+
16
+ private
17
+
18
+ def dc_global_var?(global_var)
19
+ DC_GLOBALS.include?(global_var)
20
+ end
21
+
22
+ end
23
+ end
24
+ end
@@ -3,46 +3,46 @@
3
3
  require 'pathname'
4
4
 
5
5
  module RuboCop
6
- module Cop
7
- module Sketchup
8
- module ExtensionProject
9
-
10
- # @return [Pathname]
11
- def config_path
12
- path = config.instance_variable_get(:@loaded_path)
13
- if path
14
- Pathname.new(path).expand_path.dirname
15
- else
16
- Pathname.new(Dir.pwd).expand_path
17
- end
18
- end
19
-
20
- # @return [Pathname]
21
- def relative_source_path
22
- Pathname.new(cop_config['SourcePath'] || 'src')
6
+ module SketchUp
7
+ module ExtensionProject
8
+
9
+ include SketchUp::Config
10
+
11
+ # @return [Pathname]
12
+ def config_path
13
+ path = config.instance_variable_get(:@loaded_path)
14
+ if path
15
+ Pathname.new(path).expand_path.dirname
16
+ else
17
+ Pathname.new(Dir.pwd).expand_path
23
18
  end
19
+ end
24
20
 
25
- # @return [Pathname]
26
- def source_path
27
- config_path.join(relative_source_path)
28
- end
21
+ # @return [Pathname]
22
+ def relative_source_path
23
+ Pathname.new(sketchup_source_path_config)
24
+ end
29
25
 
30
- # @param [RuboCop::ProcessedSource] processed_source
31
- def path_relative_to_source(processed_source)
32
- source_filename = processed_source.buffer.name
33
- rel_path = config.path_relative_to_config(source_filename)
34
- path = Pathname.new(rel_path).expand_path
35
- path.relative_path_from(source_path)
36
- end
26
+ # @return [Pathname]
27
+ def source_path
28
+ config_path.join(relative_source_path)
29
+ end
37
30
 
38
- # @param [RuboCop::ProcessedSource] processed_source
39
- def root_file?(processed_source)
40
- filename = path_relative_to_source(processed_source)
41
- filename.extname.downcase == '.rb' &&
42
- filename.parent.to_s == '.'
43
- end
31
+ # @param [RuboCop::ProcessedSource] processed_source
32
+ def path_relative_to_source(processed_source)
33
+ source_filename = processed_source.buffer.name
34
+ rel_path = config.path_relative_to_config(source_filename)
35
+ path = Pathname.new(rel_path).expand_path
36
+ path.relative_path_from(source_path)
37
+ end
44
38
 
39
+ # @param [RuboCop::ProcessedSource] processed_source
40
+ def root_file?(processed_source)
41
+ filename = path_relative_to_source(processed_source)
42
+ filename.extname.downcase == '.rb' &&
43
+ filename.parent.to_s == '.'
45
44
  end
45
+
46
46
  end
47
47
  end
48
48
  end
@@ -0,0 +1,239 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'cgi'
4
+ require 'erb'
5
+ require 'ostruct'
6
+ require 'base64'
7
+ require 'rubocop/formatter/text_util'
8
+
9
+ module RuboCop
10
+ module Formatter
11
+ # This formatter saves the output as an html file.
12
+ class ExtensionReviewFormatter < BaseFormatter
13
+ ELLIPSES = '<span class="extra-code">...</span>'.freeze
14
+ TEMPLATE_PATH =
15
+ File.expand_path('../../../../../assets/output.html.erb', __FILE__)
16
+
17
+ Color = Struct.new(:red, :green, :blue, :alpha) do
18
+ def to_s
19
+ "rgba(#{values.join(', ')})"
20
+ end
21
+
22
+ def fade_out(amount)
23
+ dup.tap do |color|
24
+ color.alpha -= amount
25
+ end
26
+ end
27
+ end
28
+
29
+ attr_reader :categories, :files, :summary
30
+
31
+ def initialize(output, options = {})
32
+ super
33
+ @categories = {}
34
+ @files = []
35
+ @summary = OpenStruct.new(offense_count: 0)
36
+ end
37
+
38
+ def started(target_files)
39
+ summary.target_files = target_files
40
+ end
41
+
42
+ def file_finished(file, offenses)
43
+ files << file
44
+ offenses.each { |offense|
45
+ report = OpenStruct.new(path: file, offense: offense)
46
+ categories[offense.cop_name] ||= []
47
+ categories[offense.cop_name] << report
48
+ }
49
+ summary.offense_count += offenses.count
50
+ end
51
+
52
+ def finished(inspected_files)
53
+ summary.inspected_files = inspected_files
54
+
55
+ render_html
56
+ end
57
+
58
+ def render_html
59
+ context = ERBContext.new(categories, files, summary)
60
+
61
+ template = File.read(TEMPLATE_PATH, encoding: Encoding::UTF_8)
62
+ erb = ERB.new(template, nil, '-')
63
+ html = erb.result(context.binding)
64
+
65
+ output.write html
66
+ end
67
+
68
+ # This class provides helper methods used in the ERB template.
69
+ class ERBContext
70
+ include PathUtil
71
+ include TextUtil
72
+
73
+ SEVERITY_COLORS = {
74
+ refactor: Color.new(0xED, 0x9C, 0x28, 1.0),
75
+ convention: Color.new(0xED, 0x9C, 0x28, 1.0),
76
+ warning: Color.new(0x96, 0x28, 0xEF, 1.0),
77
+ error: Color.new(0xD2, 0x32, 0x2D, 1.0),
78
+ fatal: Color.new(0xD2, 0x32, 0x2D, 1.0)
79
+ }.freeze
80
+
81
+ LOGO_IMAGE_PATH =
82
+ File.expand_path('../../../../../assets/logo.png', __FILE__)
83
+
84
+ SORT_ORDER = %w[
85
+ SketchupRequirements
86
+ SketchupDeprecations
87
+ SketchupPerformance
88
+ SketchupSuggestions
89
+ ]
90
+
91
+ DEPARTMENT_DESCRIPTIONS = {
92
+ 'SketchupRequirements' => <<-DESCRIPTION,
93
+ This is the most important set of checks. They represent a large
94
+ part of the technical requirements an extension must pass in order
95
+ to be hosted on Extension Warehouse.
96
+
97
+ They have been designed to prevent extensions from conflicting with
98
+ each other as well as avoiding bad side-effects for the end user.
99
+
100
+ Please address these as soon as possible.
101
+ DESCRIPTION
102
+ 'SketchupDeprecations' => <<-DESCRIPTION,
103
+ This department checks for usage of deprecated features. It's
104
+ recommended that you migrate your code away from deprecated features
105
+ of the SketchUp API.
106
+
107
+ This department is not a requirement for submission to
108
+ Extension Warehouse.
109
+ DESCRIPTION
110
+ 'SketchupPerformance' => <<-DESCRIPTION,
111
+ This department looks for known patterns that have noticeable
112
+ performance impact on SketchUp and/or your extension. It's worth
113
+ looking into these warnings and investigate whether performance
114
+ can be improved.
115
+
116
+ This department is not a requirement for submission to
117
+ Extension Warehouse.
118
+ DESCRIPTION
119
+ 'SketchupSuggestions' => <<-DESCRIPTION,
120
+ This department is a collection of suggestions for best practices
121
+ that aim to improve the general quality of your extension. Some of
122
+ these might be more noisy than the rest of the cops. Disable as
123
+ needed after reviewing the suggestions.
124
+
125
+ This department is not a requirement for submission to
126
+ Extension Warehouse.
127
+ DESCRIPTION
128
+ }
129
+
130
+ attr_reader :categories, :files, :summary
131
+
132
+ def initialize(categories, files, summary)
133
+ @categories = sort_categories(categories)
134
+ @files = files.sort
135
+ @summary = summary
136
+ end
137
+
138
+ def department(cop_name)
139
+ cop_name.split('/').first
140
+ end
141
+
142
+ def department_description(cop_name)
143
+ dep = department(cop_name)
144
+ text = DEPARTMENT_DESCRIPTIONS[dep] || 'MISSING DESCRIPTION'
145
+ format_plain_text(text)
146
+ end
147
+
148
+ def department_offense_count(cop_name)
149
+ dep = department(cop_name)
150
+ count = 0
151
+ categories.each { |category, offenses|
152
+ next unless department(category) == dep
153
+ count += offenses.size
154
+ }
155
+ count
156
+ end
157
+
158
+ def new_department?(cop_name)
159
+ @processed_departments ||= Set.new
160
+ dep = department(cop_name)
161
+ unless @processed_departments.include?(dep)
162
+ @processed_departments << dep
163
+ return true
164
+ end
165
+ false
166
+ end
167
+
168
+ def format_plain_text(text)
169
+ paragraphs = text.split(/(\n\r|\r\n|\r|\n){2,}/m)
170
+ "<p>#{paragraphs.join('</p><p>')}</p>"
171
+ end
172
+
173
+ def sort_categories(categories)
174
+ categories.sort { |a, b|
175
+ # First sort departments by custom ordering (of importance).
176
+ # Then sort by cop name.
177
+ a_department, a_name = a[0].split('/')
178
+ b_department, b_name = b[0].split('/')
179
+ n = SORT_ORDER.index(a_department) <=> SORT_ORDER.index(b_department)
180
+ n == 0 ? a_name <=> b_name : n
181
+ }.to_h
182
+ end
183
+
184
+ # Make Kernel#binding public.
185
+ def binding
186
+ super
187
+ end
188
+
189
+ def decorated_message(offense)
190
+ offense.message.gsub(/`(.+?)`/) do
191
+ "<code>#{Regexp.last_match(1)}</code>"
192
+ end
193
+ end
194
+
195
+ def highlighted_source_line(offense)
196
+ source_before_highlight(offense) +
197
+ hightlight_source_tag(offense) +
198
+ source_after_highlight(offense) +
199
+ possible_ellipses(offense.location)
200
+ end
201
+
202
+ def hightlight_source_tag(offense)
203
+ "<span class=\"highlight #{offense.severity}\">" \
204
+ "#{escape(offense.highlighted_area.source)}" \
205
+ '</span>'
206
+ end
207
+
208
+ def source_before_highlight(offense)
209
+ source_line = offense.location.source_line
210
+ escape(source_line[0...offense.highlighted_area.begin_pos])
211
+ end
212
+
213
+ def source_after_highlight(offense)
214
+ source_line = offense.location.source_line
215
+ escape(source_line[offense.highlighted_area.end_pos..-1])
216
+ end
217
+
218
+ def possible_ellipses(location)
219
+ location.first_line == location.last_line ? '' : " #{ELLIPSES}"
220
+ end
221
+
222
+ def cop_anchor(cop_name)
223
+ title = cop_name.downcase
224
+ title.tr!('/', '_')
225
+ "offense_#{title}"
226
+ end
227
+
228
+ def escape(s)
229
+ CGI.escapeHTML(s)
230
+ end
231
+
232
+ def base64_encoded_logo_image
233
+ image = File.read(LOGO_IMAGE_PATH, binmode: true)
234
+ Base64.encode64(image)
235
+ end
236
+ end
237
+ end
238
+ end
239
+ end
@@ -7,7 +7,7 @@ module RuboCop
7
7
  def self.defaults!
8
8
  path = CONFIG_DEFAULT.to_s
9
9
  hash = ConfigLoader.send(:load_yaml_configuration, path)
10
- config = Config.new(hash, path)
10
+ config = RuboCop::Config.new(hash, path)
11
11
  puts "configuration from #{path}" if ConfigLoader.debug?
12
12
  config = ConfigLoader.merge_with_default(config, path)
13
13
  ConfigLoader.instance_variable_set(:@default_configuration, config)
@@ -1,48 +1,46 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module RuboCop
4
- module Cop
5
- module SketchUp
6
- class Namespace
7
-
8
- attr_reader :namespace
9
-
10
- SEPARATOR = '::'.freeze
11
-
12
- # @param [String] namespace
13
- def initialize(namespace)
14
- raise TypeError unless namespace.is_a?(String)
15
- @namespace = namespace
16
- end
17
-
18
- # Get the first component of a namespace relative to Object.
19
- # May return 'Object' if the namespace is in the global namespace.
20
- def first
21
- parts.find { |name| name != 'Object' } || 'Object'
22
- end
23
-
24
- # Get a namespace string that is relative to Object.
25
- def from_root
26
- items = parts
27
- items.shift if items.size > 1 && items.first == 'Object'
28
- items.join(SEPARATOR)
29
- end
30
-
31
- def join(other)
32
- self.class.new("#{@namespace}#{SEPARATOR}#{other}")
33
- end
34
-
35
- # Get the first component of a namespace relative to Object.
36
- # May return 'Object' if the namespace is in the global namespace.
37
- def parts
38
- namespace.split(SEPARATOR)
39
- end
40
-
41
- def top_level?
42
- parts.last == 'Object'
43
- end
4
+ module SketchUp
5
+ class Namespace
44
6
 
7
+ attr_reader :namespace
8
+
9
+ SEPARATOR = '::'.freeze
10
+
11
+ # @param [String] namespace
12
+ def initialize(namespace)
13
+ raise TypeError unless namespace.is_a?(String)
14
+ @namespace = namespace
15
+ end
16
+
17
+ # Get the first component of a namespace relative to Object.
18
+ # May return 'Object' if the namespace is in the global namespace.
19
+ def first
20
+ parts.find { |name| name != 'Object' } || 'Object'
45
21
  end
22
+
23
+ # Get a namespace string that is relative to Object.
24
+ def from_root
25
+ items = parts
26
+ items.shift if items.size > 1 && items.first == 'Object'
27
+ items.join(SEPARATOR)
28
+ end
29
+
30
+ def join(other)
31
+ self.class.new("#{@namespace}#{SEPARATOR}#{other}")
32
+ end
33
+
34
+ # Get the first component of a namespace relative to Object.
35
+ # May return 'Object' if the namespace is in the global namespace.
36
+ def parts
37
+ namespace.split(SEPARATOR)
38
+ end
39
+
40
+ def top_level?
41
+ parts.last == 'Object'
42
+ end
43
+
46
44
  end
47
45
  end
48
46
  end
@@ -1,45 +1,43 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module RuboCop
4
- module Cop
5
- module Sketchup
6
- module NamespaceChecker
7
-
8
- def on_class(node)
9
- check_namespace(node)
10
- end
11
-
12
- def on_module(node)
13
- check_namespace(node)
14
- end
15
-
16
- def on_def(node)
17
- check_namespace(node)
18
- end
19
- alias on_defs on_def
20
-
21
- # Constant assignment.
22
- def on_casgn(node)
23
- check_namespace(node)
24
- end
25
-
26
- def check_namespace(node)
27
- add_offense(node, location: :name, severity: :error) if in_namespace?(node)
28
- end
29
-
30
- def in_namespace?(node)
31
- # parent_module_name might return nil if for instance a method is
32
- # defined within a block. (Apparently that is possible...)
33
- return false if node.parent_module_name.nil?
34
- namespace = SketchUp::Namespace.new(node.parent_module_name)
35
- namespaces.include?(namespace.first)
36
- end
37
-
38
- def namespaces
39
- raise NotImplementedError
40
- end
4
+ module SketchUp
5
+ module NamespaceChecker
41
6
 
7
+ def on_class(node)
8
+ check_namespace(node)
42
9
  end
10
+
11
+ def on_module(node)
12
+ check_namespace(node)
13
+ end
14
+
15
+ def on_def(node)
16
+ check_namespace(node)
17
+ end
18
+ alias on_defs on_def
19
+
20
+ # Constant assignment.
21
+ def on_casgn(node)
22
+ check_namespace(node)
23
+ end
24
+
25
+ def check_namespace(node)
26
+ add_offense(node, location: :name, severity: :error) if in_namespace?(node)
27
+ end
28
+
29
+ def in_namespace?(node)
30
+ # parent_module_name might return nil if for instance a method is
31
+ # defined within a block. (Apparently that is possible...)
32
+ return false if node.parent_module_name.nil?
33
+ namespace = SketchUp::Namespace.new(node.parent_module_name)
34
+ namespaces.include?(namespace.first)
35
+ end
36
+
37
+ def namespaces
38
+ raise NotImplementedError
39
+ end
40
+
43
41
  end
44
42
  end
45
43
  end
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module RuboCop
4
- module Cop
4
+ module SketchUp
5
5
  module NoCommentDisable
6
6
 
7
7
  private
@@ -4,7 +4,7 @@ module RuboCop
4
4
  module Cop
5
5
  module SketchupPerformance
6
6
  class Typename < Cop
7
- MSG = '.typename is very slow, prefer .is_a? instead.'.freeze
7
+ MSG = '`.typename` is very slow, prefer `.is_a?` instead.'.freeze
8
8
 
9
9
  def on_send(node)
10
10
  _, method_name = *node
@@ -5,8 +5,8 @@ module RuboCop
5
5
  module SketchupRequirements
6
6
  class ApiNamespace < Cop
7
7
 
8
- include NoCommentDisable
9
- include Sketchup::NamespaceChecker
8
+ include SketchUp::NoCommentDisable
9
+ include SketchUp::NamespaceChecker
10
10
 
11
11
  MSG = 'Do not modify the SketchUp API.'.freeze
12
12
 
@@ -10,7 +10,7 @@ module RuboCop
10
10
  # Use `return`, `next` or `break` instead.
11
11
  class Exit < Cop
12
12
 
13
- include NoCommentDisable
13
+ include SketchUp::NoCommentDisable
14
14
 
15
15
  MSG = 'Exit attempts to kill the Ruby interpreter. Use return, next or break instead.'.freeze
16
16
 
@@ -20,7 +20,7 @@ module RuboCop
20
20
  PATTERN
21
21
 
22
22
  def on_send(node)
23
- add_offense(node, location: :expression) if exit?(node)
23
+ add_offense(node, location: :expression, severity: :error) if exit?(node)
24
24
  end
25
25
  end
26
26
  end
@@ -9,7 +9,7 @@ module RuboCop
9
9
  module SketchupRequirements
10
10
  class ExtensionNamespace < Cop
11
11
 
12
- include NoCommentDisable
12
+ include SketchUp::NoCommentDisable
13
13
  include SketchUp
14
14
 
15
15
  def on_class(node)
@@ -55,7 +55,7 @@ module RuboCop
55
55
 
56
56
  def message(node)
57
57
  namespace = Namespace.new(node.defined_module_name).from_root
58
- format('Use a single root namespace. (Found %s; Previously found %s)', namespace, @@namespace)
58
+ format('Use a single root namespace. (Found `%s`; Previously found `%s`)', namespace, @@namespace)
59
59
  end
60
60
 
61
61
  end
@@ -7,8 +7,8 @@ module RuboCop
7
7
  # single root .rb file and a support folder with matching name.
8
8
  class FileStructure < Cop
9
9
 
10
- include Sketchup::ExtensionProject
11
- include NoCommentDisable
10
+ include SketchUp::NoCommentDisable
11
+ include SketchUp::ExtensionProject
12
12
 
13
13
  IGNORED_DIRECTORIES = %w[
14
14
  __MACOSX
@@ -27,7 +27,10 @@ module RuboCop
27
27
  # Ensure there is only one root Ruby file.
28
28
  if root_ruby_files.size != 1
29
29
  msg = "Extensions must have exactly one root Ruby (.rb) file. Found: %d"
30
- add_offense(nil, location: range, message: format(msg, root_ruby_files.size))
30
+ add_offense(nil,
31
+ location: range,
32
+ message: format(msg, root_ruby_files.size),
33
+ severity: :error)
31
34
  return
32
35
  end
33
36
 
@@ -42,7 +45,10 @@ module RuboCop
42
45
  # Ensure there is only one sub-directory.
43
46
  if sub_folders.size != 1
44
47
  msg = "Extensions must have exactly one support directory. Found %d"
45
- add_offense(nil, location: range, message: format(msg, sub_folders.size))
48
+ add_offense(nil,
49
+ location: range,
50
+ message: format(msg, sub_folders.size),
51
+ severity: :error)
46
52
  return
47
53
  end
48
54
 
@@ -51,7 +57,10 @@ module RuboCop
51
57
  unless support_directory.basename.to_s == extension_basename
52
58
  msg = 'Extensions must have a support directory matching the name of the root Ruby file. Expected %s, found %s'
53
59
  msg = format(msg, extension_basename, support_directory.basename)
54
- add_offense(nil, location: range, message: msg)
60
+ add_offense(nil,
61
+ location: range,
62
+ message: msg,
63
+ severity: :error)
55
64
  end
56
65
  end
57
66
 
@@ -5,7 +5,7 @@ module RuboCop
5
5
  module SketchupRequirements
6
6
  class GlobalConstants < Cop
7
7
 
8
- include NoCommentDisable
8
+ include SketchUp::NoCommentDisable
9
9
  include SketchUp
10
10
 
11
11
  MSG = 'Do not introduce global constants.'.freeze
@@ -5,7 +5,7 @@ module RuboCop
5
5
  module SketchupRequirements
6
6
  class GlobalMethods < Cop
7
7
 
8
- include NoCommentDisable
8
+ include SketchUp::NoCommentDisable
9
9
  include SketchUp
10
10
 
11
11
  MSG = 'Do not introduce global methods.'.freeze
@@ -10,12 +10,15 @@ module RuboCop
10
10
  #
11
11
  # Note that backreferences like $1, $2, etc are not global variables.
12
12
  class GlobalVariables < Cop
13
- include NoCommentDisable
13
+
14
+ include SketchUp::NoCommentDisable
15
+ include SketchUp::DynamicComponentGlobals
16
+
14
17
  MSG = 'Do not introduce global variables.'.freeze
15
18
 
16
19
  # predefined global variables their English aliases
17
20
  # http://www.zenspider.com/Languages/Ruby/QuickRef.html
18
- BUILT_IN_VARS = %w(
21
+ BUILT_IN_VARS = %i[
19
22
  $: $LOAD_PATH
20
23
  $" $LOADED_FEATURES
21
24
  $0 $PROGRAM_NAME
@@ -42,21 +45,30 @@ module RuboCop
42
45
  $DEBUG $FILENAME $VERBOSE $SAFE
43
46
  $-0 $-a $-d $-F $-i $-I $-l $-p $-v $-w
44
47
  $CLASSPATH $JRUBY_VERSION $JRUBY_REVISION $ENV_JAVA
45
- ).map(&:to_sym)
48
+ ]
46
49
 
47
- # TODO: This should probably be read only.
48
- SKETCHUP_VARS = %w(
50
+ SKETCHUP_VARS = %i[
49
51
  $loaded_files
50
- ).map(&:to_sym)
52
+ ]
53
+
54
+ # Some globals, like DC's, are being read from so often that it's better
55
+ # to ignore these to reduce noise.
56
+ READ_ONLY_VARS = DC_GLOBALS
51
57
 
52
58
  ALLOWED_VARS = BUILT_IN_VARS | SKETCHUP_VARS
53
59
 
60
+
54
61
  def allowed_var?(global_var)
55
62
  ALLOWED_VARS.include?(global_var)
56
63
  end
57
64
 
65
+ def read_allowed?(global_var)
66
+ READ_ONLY_VARS.include?(global_var)
67
+ end
68
+
58
69
  def on_gvar(node)
59
- check(node)
70
+ global_var, = *node
71
+ check(node) unless read_allowed?(global_var)
60
72
  end
61
73
 
62
74
  def on_gvasgn(node)
@@ -8,6 +8,8 @@ module RuboCop
8
8
  # They are still in use due to compatibility reasons.
9
9
  class LanguageHandlerGlobals < Cop
10
10
 
11
+ include SketchUp::NoCommentDisable
12
+
11
13
  MSG = "Avoid globals in general, but especially these which are known to be in use.".freeze
12
14
 
13
15
  LH_GLOBALS = %i[
@@ -5,7 +5,7 @@ module RuboCop
5
5
  module SketchupRequirements
6
6
  class LoadPath < Cop
7
7
 
8
- include NoCommentDisable
8
+ include SketchUp::NoCommentDisable
9
9
 
10
10
  MSG = 'Do not modify the load path.'.freeze
11
11
 
@@ -9,8 +9,8 @@ module RuboCop
9
9
  # Extensions should not load additional files when it's disabled.
10
10
  class MinimalRegistration < Cop
11
11
 
12
- include Sketchup::ExtensionProject
13
- include NoCommentDisable
12
+ include SketchUp::NoCommentDisable
13
+ include SketchUp::ExtensionProject
14
14
 
15
15
  MSG = "Don't load extension files in the root file registering the extension.".freeze
16
16
 
@@ -39,7 +39,7 @@ module RuboCop
39
39
  filename = require_filename(node)
40
40
  return if filename.nil?
41
41
  return unless extension_file?(filename)
42
- add_offense(node)
42
+ add_offense(node, severity: :error)
43
43
  end
44
44
 
45
45
  end
@@ -7,7 +7,7 @@ module RuboCop
7
7
  # confuse users to think the extension isn't working.
8
8
  class RegisterExtension < Cop
9
9
 
10
- include NoCommentDisable
10
+ include SketchUp::NoCommentDisable
11
11
 
12
12
  MSG = 'Always register extensions to load by default.'.freeze
13
13
 
@@ -20,12 +20,12 @@ module RuboCop
20
20
  def on_send(node)
21
21
  sketchup_register_extension(node).each { |args|
22
22
  if args.size < 2
23
- add_offense(node)
23
+ add_offense(node, severity: :error)
24
24
  next
25
25
  end
26
26
  load_arg = args[1]
27
27
  next if load_arg.true_type?
28
- add_offense(load_arg)
28
+ add_offense(load_arg, severity: :error)
29
29
  }
30
30
  end
31
31
 
@@ -0,0 +1,74 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module SketchupRequirements
6
+ # Due to how require and Sketchup.require checks whether a file has been
7
+ # loaded, files from SketchUp's Tools folder should be required in using
8
+ # `require` and with their file extension to avoid duplicate loading.
9
+ class RequireToolsRubyFiles < Cop
10
+
11
+ include SketchUp::NoCommentDisable
12
+
13
+ MSG = "Require files from SketchUp's Tools directory with require.".freeze
14
+
15
+ RUBY_FILES = %w[extensions.rb langhandler.rb sketchup.rb]
16
+
17
+
18
+ def_node_matcher :ruby_require, <<-PATTERN
19
+ (send nil? :require (str $_))
20
+ PATTERN
21
+
22
+ def_node_matcher :ruby_require?, <<-PATTERN
23
+ (send nil? :require (str _))
24
+ PATTERN
25
+
26
+
27
+ def_node_matcher :sketchup_require, <<-PATTERN
28
+ (send
29
+ (const nil? :Sketchup) :require
30
+ (str $_)
31
+ )
32
+ PATTERN
33
+
34
+ def_node_matcher :sketchup_require?, <<-PATTERN
35
+ (send
36
+ (const nil? :Sketchup) :require
37
+ (str _)
38
+ )
39
+ PATTERN
40
+
41
+
42
+ def on_send(node)
43
+ if ruby_require?(node)
44
+ # The Tools folder Ruby files should be loaded using `require` and
45
+ # include the ".rb" file extension.
46
+ filename = ruby_require(node)
47
+ return unless tools_file_required?(filename)
48
+ return if expected_require_syntax?(filename)
49
+ add_offense(node, location: :expression, severity: :error)
50
+ elsif sketchup_require?(node)
51
+ # The Tools folder Ruby files should not be loaded using
52
+ # `Sketchup.require`.
53
+ filename = sketchup_require(node)
54
+ return unless tools_file_required?(filename)
55
+ add_offense(node, location: :expression, severity: :error)
56
+ end
57
+ end
58
+
59
+ private
60
+
61
+ def tools_file_required?(filename)
62
+ ext = File.extname(filename)
63
+ full_filename = ext.empty? ? "#{filename}.rb" : filename
64
+ RUBY_FILES.include?(full_filename.downcase)
65
+ end
66
+
67
+ def expected_require_syntax?(filename)
68
+ RUBY_FILES.include?(filename)
69
+ end
70
+
71
+ end
72
+ end
73
+ end
74
+ end
@@ -5,8 +5,8 @@ module RuboCop
5
5
  module SketchupRequirements
6
6
  class RubyCoreNamespace < Cop
7
7
 
8
- include NoCommentDisable
9
- include Sketchup::NamespaceChecker
8
+ include SketchUp::NoCommentDisable
9
+ include SketchUp::NamespaceChecker
10
10
 
11
11
  MSG = 'Do not modify Ruby core functionality.'.freeze
12
12
 
@@ -5,8 +5,8 @@ module RuboCop
5
5
  module SketchupRequirements
6
6
  class RubyStdLibNamespace < Cop
7
7
 
8
- include NoCommentDisable
9
- include Sketchup::NamespaceChecker
8
+ include SketchUp::NoCommentDisable
9
+ include SketchUp::NamespaceChecker
10
10
 
11
11
  MSG = 'Do not modify Ruby stdlib functionality.'.freeze
12
12
 
@@ -6,8 +6,8 @@ module RuboCop
6
6
  # Don't modify SketchUp's shipped extensions.
7
7
  class ShippedExtensionsNamespace < Cop
8
8
 
9
- include NoCommentDisable
10
- include Sketchup::NamespaceChecker
9
+ include SketchUp::NoCommentDisable
10
+ include SketchUp::NamespaceChecker
11
11
 
12
12
  MSG = 'Do not modify shipped extensions.'.freeze
13
13
 
@@ -7,8 +7,8 @@ module RuboCop
7
7
  # This should be done by the root .rb file in the extension package.
8
8
  class SketchupExtension < Cop
9
9
 
10
- include Sketchup::ExtensionProject
11
- include NoCommentDisable
10
+ include SketchUp::NoCommentDisable
11
+ include SketchUp::ExtensionProject
12
12
 
13
13
  MSG = 'Create and register one SketchupExtension instance per extension.'.freeze
14
14
  MSG_CREATE_ONE = 'Create only SketchupExtension instance per extension.'.freeze
@@ -42,12 +42,18 @@ module RuboCop
42
42
  # Look for SketchupExtension.new.
43
43
  extension_nodes = sketchup_extension_new(source_node).to_a
44
44
  if extension_nodes.size > 1
45
- add_offense(nil, location: range, message: MSG_CREATE_ONE)
45
+ add_offense(nil,
46
+ location: range,
47
+ message: MSG_CREATE_ONE,
48
+ severity: :error)
46
49
  return
47
50
  end
48
51
  extension_node = extension_nodes.first
49
52
  if extension_node.nil?
50
- add_offense(nil, location: range, message: MSG_CREATE_MISSING)
53
+ add_offense(nil,
54
+ location: range,
55
+ message: MSG_CREATE_MISSING,
56
+ severity: :error)
51
57
  return
52
58
  end
53
59
 
@@ -64,13 +70,19 @@ module RuboCop
64
70
  # TODO: The offences here should probably highlight the line where
65
71
  # Sketchup.register_extension is.
66
72
  if registered_vars.size > 1
67
- add_offense(nil, location: range, message: MSG_REGISTER_ONE)
73
+ add_offense(nil,
74
+ location: range,
75
+ message: MSG_REGISTER_ONE,
76
+ severity: :error)
68
77
  return
69
78
  end
70
79
  registered_var = sketchup_register_extension(source_node).first
71
80
  unless registered_var == extension_var
72
81
  msg = MSG_REGISTER_MISSING % extension_var.to_s
73
- add_offense(nil, location: range, message: msg)
82
+ add_offense(nil,
83
+ location: range,
84
+ message: msg,
85
+ severity: :error)
74
86
  end
75
87
  end
76
88
 
@@ -7,20 +7,9 @@ module RuboCop
7
7
  # change at any time.
8
8
  class DynamicComponentInternals < Cop
9
9
 
10
- MSG = "Avoid relying on internal logic of Dynamic Components.".freeze
11
-
12
- DC_GLOBALS = %i[
13
- $dc_strings
14
- $dc_extension
15
- $dc_CONFIGURATOR_NAME
16
- $dc_REPORTER_NAME
17
- $dc_MANAGER_NAME
18
- $dc_observers
19
- ]
10
+ include SketchUp::DynamicComponentGlobals
20
11
 
21
- def dc_global_var?(global_var)
22
- DC_GLOBALS.include?(global_var)
23
- end
12
+ MSG = "Avoid relying on internal logic of Dynamic Components.".freeze
24
13
 
25
14
  def on_gvar(node)
26
15
  check(node)
@@ -17,7 +17,7 @@ module RuboCop
17
17
  # basename = File.basename(file, '.*')
18
18
  class FileEncoding < Cop
19
19
 
20
- MSG = 'Beware encoding bug with __FILE__ and __dir__.'.freeze
20
+ MSG = 'Beware encoding bug with `__FILE__` and `__dir__`.'.freeze
21
21
 
22
22
  def_node_matcher :file_loaded?, <<-PATTERN
23
23
  (send nil? {:file_loaded? :file_loaded} ...)
@@ -4,6 +4,7 @@ module RuboCop
4
4
  module Cop
5
5
  module SketchupSuggestions
6
6
  class ModelEntities < Cop
7
+
7
8
  MSG = 'Typically one should use model.active_entities instead of model.entities.'.freeze
8
9
 
9
10
  # Reference: http://www.rubydoc.info/gems/rubocop/RuboCop/NodePattern
@@ -17,7 +17,7 @@ module RuboCop
17
17
  return unless operation_name.is_a?(String)
18
18
  # Check the format of the operation name.
19
19
  unless acceptable_operation_name?(operation_name)
20
- msg = %[#{MSG} Expected: "#{titleize(operation_name)}"]
20
+ msg = %[#{MSG} Expected: `"#{titleize(operation_name)}"`]
21
21
  add_offense(args.first, location: :expression, message: msg)
22
22
  end
23
23
  # Check the length of the operation name.
@@ -1,5 +1,5 @@
1
1
  module RuboCop
2
2
  module SketchUp
3
- VERSION = '0.1.5'
3
+ VERSION = '0.2.0'
4
4
  end
5
5
  end
@@ -13,7 +13,7 @@ Gem::Specification.new do |spec|
13
13
 
14
14
  spec.version = RuboCop::SketchUp::VERSION
15
15
  spec.platform = Gem::Platform::RUBY
16
- spec.required_ruby_version = '>= 2.0.0'
16
+ spec.required_ruby_version = '>= 2.1.0'
17
17
 
18
18
  spec.require_paths = ['lib']
19
19
  spec.files = Dir[
@@ -23,6 +23,8 @@ Gem::Specification.new do |spec|
23
23
  'Gemfile'
24
24
  ]
25
25
 
26
- spec.add_dependency 'rubocop', '~> 0.51.0'
26
+ # NOTE: Remember to update the README.md instructions on how to install
27
+ # compatible RuboCop version.
28
+ spec.add_dependency 'rubocop', '~> 0.52.0'
27
29
  spec.add_development_dependency 'bundler', '~> 1.13'
28
30
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rubocop-sketchup
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.5
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Trimble Inc, SketchUp Team
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2017-12-08 00:00:00.000000000 Z
11
+ date: 2018-03-02 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rubocop
@@ -16,14 +16,14 @@ dependencies:
16
16
  requirements:
17
17
  - - "~>"
18
18
  - !ruby/object:Gem::Version
19
- version: 0.51.0
19
+ version: 0.52.0
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
- version: 0.51.0
26
+ version: 0.52.0
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: bundler
29
29
  requirement: !ruby/object:Gem::Requirement
@@ -48,8 +48,11 @@ files:
48
48
  - config/default.yml
49
49
  - lib/rubocop-sketchup.rb
50
50
  - lib/rubocop/sketchup.rb
51
+ - lib/rubocop/sketchup/config.rb
52
+ - lib/rubocop/sketchup/dc_globals.rb
51
53
  - lib/rubocop/sketchup/deprecations/operation_next_transparent.rb
52
54
  - lib/rubocop/sketchup/extension_project.rb
55
+ - lib/rubocop/sketchup/formatter/extension_review.rb
53
56
  - lib/rubocop/sketchup/inject.rb
54
57
  - lib/rubocop/sketchup/namespace.rb
55
58
  - lib/rubocop/sketchup/namespace_checker.rb
@@ -69,6 +72,7 @@ files:
69
72
  - lib/rubocop/sketchup/requirements/load_path.rb
70
73
  - lib/rubocop/sketchup/requirements/minimal_registration.rb
71
74
  - lib/rubocop/sketchup/requirements/register_extension.rb
75
+ - lib/rubocop/sketchup/requirements/require_tools_ruby_files.rb
72
76
  - lib/rubocop/sketchup/requirements/ruby_core_namespace.rb
73
77
  - lib/rubocop/sketchup/requirements/ruby_stdlib_namespace.rb
74
78
  - lib/rubocop/sketchup/requirements/shipped_extensions_namespace.rb
@@ -93,7 +97,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
93
97
  requirements:
94
98
  - - ">="
95
99
  - !ruby/object:Gem::Version
96
- version: 2.0.0
100
+ version: 2.1.0
97
101
  required_rubygems_version: !ruby/object:Gem::Requirement
98
102
  requirements:
99
103
  - - ">="
@@ -101,7 +105,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
101
105
  version: '0'
102
106
  requirements: []
103
107
  rubyforge_project:
104
- rubygems_version: 2.5.2
108
+ rubygems_version: 2.6.14
105
109
  signing_key:
106
110
  specification_version: 4
107
111
  summary: RuboCop rules for SketchUp extensions.