danger-rubocop 0.11.0 → 0.13.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 7a25805f10b9265549f39e7efb812f9b940d317727b031a8546aaa34e87a6f57
4
- data.tar.gz: 553e742ad2d0780f441a0bb443bcc24b18afd2d26a78fec7cb92b9f7b5abafe1
3
+ metadata.gz: 228bd1f2c8294e6ade21c5a33424af6ddfa0ceb08613f62ca22db33bdfecd212
4
+ data.tar.gz: cafedd57a18b594548ef05c3287fd96acbf9a5692306e291ad85f7cf3456e6ad
5
5
  SHA512:
6
- metadata.gz: df8c319c5b617f7ee5e2698e0db2d2610eeac3007dc3b5d183261295608aaba1805ae496f3b70897a7570bc79f222ed8be6f1af6859d3a0b5d2f6090377a297c
7
- data.tar.gz: 8611653f86ad51b236dd4f4b7104e4776c8748d30cfdbc47f70bad2e82580aff9e46b8b8726ccfd1261f48e719ac3459b123871864ca0ddda5ff87e86d5a06d2
6
+ metadata.gz: e30f4a47b31ce14a58e263da6a7d0cd80a81c016b989be786487393d60f45a131bbabf76f4dbbb08b22058331d1878ff30ddd917cceb3db1e34b5f3cb85fd039
7
+ data.tar.gz: 705e56f918fe479ee9633f513aaa9ce3396616456112be2d4fa2b1f9358b9c58c620f367ba8cedbb6159e5463d2bad2168e3fd1a949ff3477e6696b4fd24acc4
data/Gemfile.lock CHANGED
@@ -52,7 +52,8 @@ GEM
52
52
  faraday-net_http_persistent (1.1.0)
53
53
  ffi (1.15.0)
54
54
  formatador (0.2.5)
55
- git (1.8.1)
55
+ git (1.13.1)
56
+ addressable (~> 2.8)
56
57
  rchardet (~> 1.8)
57
58
  guard (2.16.2)
58
59
  formatador (>= 0.2.4)
@@ -107,7 +108,8 @@ GEM
107
108
  ffi (~> 1.0)
108
109
  rchardet (1.8.0)
109
110
  regexp_parser (2.1.1)
110
- rexml (3.2.5)
111
+ rexml (3.2.8)
112
+ strscan (>= 3.0.9)
111
113
  rspec (3.10.0)
112
114
  rspec-core (~> 3.10.0)
113
115
  rspec-expectations (~> 3.10.0)
@@ -138,11 +140,12 @@ GEM
138
140
  addressable (>= 2.3.5)
139
141
  faraday (> 0.8, < 2.0)
140
142
  shellany (0.0.1)
143
+ strscan (3.1.0)
141
144
  terminal-table (3.0.1)
142
145
  unicode-display_width (>= 1.1.1, < 3)
143
146
  thor (1.1.0)
144
147
  unicode-display_width (2.0.0)
145
- yard (0.9.26)
148
+ yard (0.9.36)
146
149
 
147
150
  PLATFORMS
148
151
  ruby
data/README.md CHANGED
@@ -57,15 +57,17 @@ The following keys are supported:
57
57
  (this option will instruct rubocop to ignore the files that your rubocop config ignores,
58
58
  despite the plugin providing the list of files explicitly)
59
59
  * `inline_comment`: pass `true` to comment inline of the diffs.
60
+ * `group_inline_comments`: pass `true` to group inline comments to be a single comment on each line with all issues for that line.
60
61
  * `fail_on_inline_comment`: pass `true` to use `fail` instead of `warn` on inline comment.
61
62
  * `report_severity`: pass `true` to use `fail` or `warn` based on Rubocop severity.
62
63
  * `report_danger`: pass true to report errors to Danger, and break CI.
64
+ * `include_cop_names`: pass true to include cop names when reporting errors with `report_danger`.
63
65
  * `config`: path to the `.rubocop.yml` file.
64
66
  * `only_report_new_offenses`: pass `true` to only report offenses that are in current user's scope.
65
67
  Note that this won't mark offenses for _Metrics/XXXLength_ if you add lines to an already existing scope.
66
68
  * `include_cop_names`: Prepends cop names to the output messages. Example: "Layout/EmptyLinesAroundBlockBody: Extra empty line detected at block body end."
67
69
  * `rubocop_cmd`: Allows you to change the rubocop executable that's invoked. This is used to support rubocop wrappers like [Standard](https://github.com/testdouble/standard/) by passing `standardrb` as the value.
68
-
70
+ * `skip_bundle_exec`: When there is a `Gemfile` in the project, Rubocop will be executed using [Bundler](https://bundler.io). When `true`, this flag will force Rubocop to run without `bundle exec`.
69
71
 
70
72
  Passing `files` as only argument is also supported for backward compatibility.
71
73
 
data/lib/danger_plugin.rb CHANGED
@@ -34,18 +34,22 @@ module Danger
34
34
  report_danger = config[:report_danger] || false
35
35
  only_report_new_offenses = config[:only_report_new_offenses] || false
36
36
  inline_comment = config[:inline_comment] || false
37
+ group_inline_comments = config[:group_inline_comments] || false
37
38
  fail_on_inline_comment = config[:fail_on_inline_comment] || false
38
39
  report_severity = config[:report_severity] || false
39
40
  include_cop_names = config[:include_cop_names] || false
40
41
  rubocop_cmd = config[:rubocop_cmd] || 'rubocop'
42
+ skip_bundle_exec = config[:skip_bundle_exec] || false
41
43
 
42
44
  files_to_lint = fetch_files_to_lint(files)
43
- files_to_report = rubocop(files_to_lint, force_exclusion, only_report_new_offenses, cmd: rubocop_cmd, config_path: config_path)
45
+ files_to_report = rubocop(files_to_lint, force_exclusion, only_report_new_offenses, cmd: rubocop_cmd, config_path: config_path, skip_bundle_exec: skip_bundle_exec)
44
46
 
45
47
  return if files_to_report.empty?
46
- return report_failures files_to_report if report_danger
48
+ return report_failures(files_to_report, include_cop_names: include_cop_names) if report_danger
47
49
 
48
- if inline_comment
50
+ if inline_comment && group_inline_comments
51
+ add_grouped_violation_for_each_line(files_to_report, fail_on_inline_comment, report_severity, include_cop_names: include_cop_names)
52
+ elsif inline_comment
49
53
  add_violation_for_each_line(files_to_report, fail_on_inline_comment, report_severity, include_cop_names: include_cop_names)
50
54
  else
51
55
  markdown offenses_message(files_to_report, include_cop_names: include_cop_names)
@@ -54,12 +58,12 @@ module Danger
54
58
 
55
59
  private
56
60
 
57
- def rubocop(files_to_lint, force_exclusion, only_report_new_offenses, cmd: 'rubocop', config_path: nil)
61
+ def rubocop(files_to_lint, force_exclusion, only_report_new_offenses, cmd: 'rubocop', config_path: nil, skip_bundle_exec: false)
58
62
  base_command = [cmd, '-f', 'json', '--only-recognized-file-types']
59
63
  base_command.concat(['--force-exclusion']) if force_exclusion
60
64
  base_command.concat(['--config', config_path.shellescape]) unless config_path.nil?
61
65
 
62
- rubocop_output = `#{'bundle exec ' if File.exist?('Gemfile')}#{base_command.join(' ')} #{files_to_lint}`
66
+ rubocop_output = `#{'bundle exec ' if File.exist?('Gemfile') && !skip_bundle_exec}#{base_command.join(' ')} #{files_to_lint}`
63
67
 
64
68
  return [] if rubocop_output.empty?
65
69
 
@@ -108,8 +112,7 @@ module Danger
108
112
  style: { border_i: '|' },
109
113
  rows: offending_files.flat_map do |file|
110
114
  file['offenses'].map do |offense|
111
- offense_message = offense['message']
112
- offense_message = offense['cop_name'] + ': ' + offense_message if include_cop_names
115
+ offense_message = offense_message(offense, include_cop_names: include_cop_names)
113
116
  [file['path'], offense['location']['line'], offense_message]
114
117
  end
115
118
  end
@@ -117,10 +120,11 @@ module Danger
117
120
  message + table.split("\n")[1..-2].join("\n")
118
121
  end
119
122
 
120
- def report_failures(offending_files)
123
+ def report_failures(offending_files, include_cop_names: false)
121
124
  offending_files.each do |file|
122
125
  file['offenses'].each do |offense|
123
- fail "#{file['path']} | #{offense['location']['line']} | #{offense['message']}"
126
+ offense_message = offense_message(offense, include_cop_names: include_cop_names)
127
+ fail "#{file['path']} | #{offense['location']['line']} | #{offense_message}"
124
128
  end
125
129
  end
126
130
  end
@@ -128,8 +132,7 @@ module Danger
128
132
  def add_violation_for_each_line(offending_files, fail_on_inline_comment, report_severity, include_cop_names: false)
129
133
  offending_files.flat_map do |file|
130
134
  file['offenses'].map do |offense|
131
- offense_message = offense['message']
132
- offense_message = offense['cop_name'] + ': ' + offense_message if include_cop_names
135
+ offense_message = offense_message(offense, include_cop_names: include_cop_names)
133
136
  kargs = {
134
137
  file: file['path'],
135
138
  line: offense['location']['line']
@@ -145,6 +148,37 @@ module Danger
145
148
  end
146
149
  end
147
150
 
151
+ def add_grouped_violation_for_each_line(offending_files, fail_on_inline_comment, report_severity, include_cop_names: false)
152
+ grouped_offense_messages = Hash.new { |h, k| h[k] = [] }
153
+ offending_files.flat_map do |file|
154
+ file['offenses'].map do |offense|
155
+ offense_message = offense_message(offense, include_cop_names: include_cop_names)
156
+ kargs = {
157
+ file: file['path'],
158
+ line: offense['location']['line']
159
+ }
160
+ grouped_offense_messages[kargs] << offense_message
161
+ end
162
+ end
163
+
164
+ grouped_offense_messages.each do |kargs, offense_messages|
165
+ grouped_offense_message = if offense_messages.length > 1
166
+ "\n" + offense_messages.map do |offense_message|
167
+ "* #{offense_message}"
168
+ end.join("\n")
169
+ else
170
+ offense_messages[0]
171
+ end
172
+ if fail_on_inline_comment
173
+ fail(grouped_offense_message, **kargs)
174
+ elsif report_severity && %w[error fatal].include?(offense['severity'])
175
+ fail(grouped_offense_message, **kargs)
176
+ else
177
+ warn(grouped_offense_message, **kargs)
178
+ end
179
+ end
180
+ end
181
+
148
182
  def fetch_files_to_lint(files = nil)
149
183
  to_lint = if files.nil?
150
184
  # when files are renamed, git.modified_files contains the old name not the new one, so we need to do the convertion
@@ -155,5 +189,11 @@ module Danger
155
189
  end
156
190
  Shellwords.join(to_lint)
157
191
  end
192
+
193
+ def offense_message(offense, include_cop_names: false)
194
+ return offense['message'] unless include_cop_names
195
+
196
+ "#{offense['cop_name']}: #{offense['message']}"
197
+ end
158
198
  end
159
199
  end
data/lib/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module DangerRubocop
2
- VERSION = '0.11.0'.freeze
2
+ VERSION = '0.13.0'.freeze
3
3
  end
@@ -299,7 +299,7 @@ EOS
299
299
  end
300
300
 
301
301
  context 'with fail_on_inline_comment option' do
302
- it 'reports violations as line by line failures' do
302
+ before do
303
303
  allow(@rubocop.git).to receive(:modified_files)
304
304
  .and_return(['spec/fixtures/ruby_file.rb'])
305
305
  allow(@rubocop.git).to receive(:added_files).and_return([])
@@ -307,64 +307,176 @@ EOS
307
307
  allow(@rubocop).to receive(:`)
308
308
  .with('bundle exec rubocop -f json --only-recognized-file-types spec/fixtures/ruby_file.rb')
309
309
  .and_return(response_ruby_file)
310
+ end
310
311
 
312
+ it 'reports violations as line by line failures' do
311
313
  @rubocop.lint(fail_on_inline_comment: true, inline_comment: true)
312
314
 
313
315
  expect(@rubocop.violation_report[:errors].first.to_s)
314
316
  .to eq("Violation Don't do that! { sticky: false, file: spec/fixtures/ruby_file.rb, line: 13, type: error }")
315
317
  end
316
- end
317
-
318
- context 'with report_severity option' do
319
- context 'file with error' do
320
- it 'reports violations by rubocop severity' do
321
- allow(@rubocop.git).to receive(:added_files).and_return([])
322
- allow(@rubocop.git).to receive(:modified_files)
323
- .and_return(["spec/fixtures/another_ruby_file.rb"])
324
- allow(@rubocop.git).to receive(:renamed_files).and_return([])
325
318
 
326
- allow(@rubocop).to receive(:`)
327
- .with('bundle exec rubocop -f json --only-recognized-file-types spec/fixtures/another_ruby_file.rb')
328
- .and_return(response_another_ruby_file)
319
+ it 'includes cop names when include_cop_names is set' do
320
+ @rubocop.lint(fail_on_inline_comment: true, inline_comment: true, include_cop_names: true)
329
321
 
330
- @rubocop.lint(report_severity: true, inline_comment: true)
322
+ expect(@rubocop.violation_report[:errors].first.to_s)
323
+ .to eq("Violation Syntax/WhetherYouShouldDoThat: Don't do that! { sticky: false, file: spec/fixtures/ruby_file.rb, line: 13, type: error }")
324
+ end
325
+ end
331
326
 
332
- expect(@rubocop.violation_report[:errors].first.to_s)
333
- .to eq("Violation Don't do that! { sticky: false, file: spec/fixtures/another_ruby_file.rb, line: 23, type: error }")
327
+ context 'with group_inline_comments' do
328
+ context 'with multiple violations on the same line' do
329
+ let(:response_ruby_file) do
330
+ {
331
+ 'files' => [
332
+ {
333
+ 'path' => 'spec/fixtures/ruby_file.rb',
334
+ 'offenses' => [
335
+ {
336
+ 'cop_name' => 'Syntax/WhetherYouShouldDoThat',
337
+ 'message' => "Don't do that!",
338
+ 'severity' => 'warning',
339
+ 'location' => { 'line' => 13 }
340
+ },
341
+ {
342
+ 'cop_name' => 'Syntax/WhetherYouShouldDoThat',
343
+ 'message' => "Also don't do that!",
344
+ 'severity' => 'warning',
345
+ 'location' => { 'line' => 13 }
346
+ }
347
+ ]
348
+ }
349
+ ]
350
+ }.to_json
334
351
  end
335
- end
336
352
 
337
- context 'file with warning' do
338
- it 'reports violations by rubocop severity' do
339
- allow(@rubocop.git).to receive(:added_files).and_return([])
353
+ it 'reports multiple violations grouped by line in a bulleted list' do
340
354
  allow(@rubocop.git).to receive(:modified_files)
341
- .and_return(["spec/fixtures/ruby_file.rb"])
355
+ .and_return(['spec/fixtures/ruby_file.rb'])
356
+ allow(@rubocop.git).to receive(:added_files).and_return([])
342
357
  allow(@rubocop.git).to receive(:renamed_files).and_return([])
343
-
344
358
  allow(@rubocop).to receive(:`)
345
359
  .with('bundle exec rubocop -f json --only-recognized-file-types spec/fixtures/ruby_file.rb')
346
360
  .and_return(response_ruby_file)
347
361
 
348
- @rubocop.lint(report_severity: true, inline_comment: true)
349
-
350
- expect(@rubocop.violation_report[:warnings].first.to_s)
351
- .to eq("Violation Don't do that! { sticky: false, file: spec/fixtures/ruby_file.rb, line: 13, type: warning }")
362
+ @rubocop.lint(inline_comment: true, group_inline_comments: true)
363
+
364
+ warning = @rubocop.violation_report[:warnings].first
365
+ expect(warning.file)
366
+ .to eq("spec/fixtures/ruby_file.rb")
367
+ expect(warning.line)
368
+ .to eq(13)
369
+ expect(warning.message)
370
+ .to eq(
371
+ <<~HEREDOC
372
+
373
+ * Don't do that!
374
+ * Also don't do that!
375
+ HEREDOC
376
+ .chomp
377
+ )
352
378
  end
353
379
  end
354
- end
355
380
 
356
- context 'using standardrb cmd' do
357
- it 'executes using the standardrb cmd' do
381
+ it 'reports single violations grouped by line as normal line by line warnings' do
382
+ allow(@rubocop.git).to receive(:modified_files)
383
+ .and_return(['spec/fixtures/ruby_file.rb'])
384
+ allow(@rubocop.git).to receive(:added_files).and_return([])
385
+ allow(@rubocop.git).to receive(:renamed_files).and_return([])
358
386
  allow(@rubocop).to receive(:`)
359
- .with('bundle exec standardrb -f json --only-recognized-file-types --config path/to/rubocop.yml spec/fixtures/ruby_file.rb')
387
+ .with('bundle exec rubocop -f json --only-recognized-file-types spec/fixtures/ruby_file.rb')
360
388
  .and_return(response_ruby_file)
361
389
 
362
- # Do it
363
- @rubocop.lint(files: 'spec/fixtures/ruby*.rb', rubocop_cmd: 'standardrb', config: 'path/to/rubocop.yml')
390
+ @rubocop.lint(inline_comment: true, group_inline_comments: true)
391
+
392
+ warning = @rubocop.violation_report[:warnings].first
393
+ expect(warning.file)
394
+ .to eq("spec/fixtures/ruby_file.rb")
395
+ expect(warning.line)
396
+ .to eq(13)
397
+ expect(warning.message)
398
+ .to eq("Don't do that!")
399
+ end
400
+ end
401
+ end
402
+
403
+ context 'with report_severity option' do
404
+ context 'file with error' do
405
+ it 'reports violations by rubocop severity' do
406
+ allow(@rubocop.git).to receive(:added_files).and_return([])
407
+ allow(@rubocop.git).to receive(:modified_files)
408
+ .and_return(["spec/fixtures/another_ruby_file.rb"])
409
+ allow(@rubocop.git).to receive(:renamed_files).and_return([])
410
+
411
+ allow(@rubocop).to receive(:`)
412
+ .with('bundle exec rubocop -f json --only-recognized-file-types spec/fixtures/another_ruby_file.rb')
413
+ .and_return(response_another_ruby_file)
414
+
415
+ @rubocop.lint(report_severity: true, inline_comment: true)
416
+
417
+ expect(@rubocop.violation_report[:errors].first.to_s)
418
+ .to eq("Violation Don't do that! { sticky: false, file: spec/fixtures/another_ruby_file.rb, line: 23, type: error }")
364
419
  end
365
420
  end
366
421
  end
367
422
 
423
+ context 'file with warning' do
424
+ it 'reports violations by rubocop severity' do
425
+ allow(@rubocop.git).to receive(:added_files).and_return([])
426
+ allow(@rubocop.git).to receive(:modified_files)
427
+ .and_return(["spec/fixtures/ruby_file.rb"])
428
+ allow(@rubocop.git).to receive(:renamed_files).and_return([])
429
+
430
+ allow(@rubocop).to receive(:`)
431
+ .with('bundle exec rubocop -f json --only-recognized-file-types spec/fixtures/ruby_file.rb')
432
+ .and_return(response_ruby_file)
433
+
434
+ @rubocop.lint(report_severity: true, inline_comment: true)
435
+
436
+ expect(@rubocop.violation_report[:warnings].first.to_s)
437
+ .to eq("Violation Don't do that! { sticky: false, file: spec/fixtures/ruby_file.rb, line: 13, type: warning }")
438
+ end
439
+ end
440
+
441
+ context 'using standardrb cmd' do
442
+ it 'executes using the standardrb cmd' do
443
+ allow(@rubocop).to receive(:`)
444
+ .with('bundle exec standardrb -f json --only-recognized-file-types --config path/to/rubocop.yml spec/fixtures/ruby_file.rb')
445
+ .and_return(response_ruby_file)
446
+
447
+ # Do it
448
+ @rubocop.lint(files: 'spec/fixtures/ruby*.rb', rubocop_cmd: 'standardrb', config: 'path/to/rubocop.yml')
449
+ end
450
+ end
451
+
452
+ context 'using Bundler' do
453
+ it 'uses `bundle exec` when there is a Gemfile' do
454
+ allow(@rubocop).to receive(:`)
455
+ .with('bundle exec rubocop -f json --only-recognized-file-types --config path/to/rubocop.yml spec/fixtures/ruby_file.rb')
456
+ .and_return(response_ruby_file)
457
+
458
+ @rubocop.lint(files: 'spec/fixtures/ruby*.rb', config: 'path/to/rubocop.yml')
459
+ end
460
+
461
+ it 'doesn\'t use `bundle exec` when there is no Gemfile' do
462
+ allow(File).to receive(:exist?).with('Gemfile').and_return(false)
463
+
464
+ allow(@rubocop).to receive(:`)
465
+ .with('rubocop -f json --only-recognized-file-types --config path/to/rubocop.yml spec/fixtures/ruby_file.rb')
466
+ .and_return(response_ruby_file)
467
+
468
+ @rubocop.lint(files: 'spec/fixtures/ruby*.rb', config: 'path/to/rubocop.yml')
469
+ end
470
+
471
+ it 'doesn\'t use `bundle exec` when there is a Gemfile but skip_bundle_exec is true' do
472
+ allow(@rubocop).to receive(:`)
473
+ .with('rubocop -f json --only-recognized-file-types --config path/to/rubocop.yml spec/fixtures/ruby_file.rb')
474
+ .and_return(response_ruby_file)
475
+
476
+ @rubocop.lint(files: 'spec/fixtures/ruby*.rb', config: 'path/to/rubocop.yml', skip_bundle_exec: true)
477
+ end
478
+ end
479
+
368
480
  describe 'a filename with special characters' do
369
481
  it 'is shell escaped' do
370
482
  modified_files = [
@@ -382,8 +494,7 @@ EOS
382
494
  end
383
495
 
384
496
  describe 'report to danger' do
385
- let(:fail_msg) { %{spec/fixtures/ruby_file.rb | 13 | Don't do that!} }
386
- it 'reports to danger' do
497
+ before do
387
498
  allow(@rubocop.git).to receive(:modified_files)
388
499
  .and_return(['spec/fixtures/ruby_file.rb'])
389
500
  allow(@rubocop.git).to receive(:added_files).and_return([])
@@ -391,10 +502,20 @@ EOS
391
502
  allow(@rubocop).to receive(:`)
392
503
  .with('bundle exec rubocop -f json --only-recognized-file-types spec/fixtures/ruby_file.rb')
393
504
  .and_return(response_ruby_file)
505
+ end
394
506
 
507
+ it 'reports to danger' do
508
+ fail_msg = %{spec/fixtures/ruby_file.rb | 13 | Don't do that!}
395
509
  expect(@rubocop).to receive(:fail).with(fail_msg)
396
510
  @rubocop.lint(report_danger: true)
397
511
  end
512
+
513
+ it 'includes cop names when include_cop_names is set' do
514
+ fail_msg = %{spec/fixtures/ruby_file.rb | 13 | Syntax/WhetherYouShouldDoThat: Don't do that!}
515
+
516
+ expect(@rubocop).to receive(:fail).with(fail_msg)
517
+ @rubocop.lint(report_danger: true, include_cop_names: true)
518
+ end
398
519
  end
399
520
  end
400
521
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: danger-rubocop
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.11.0
4
+ version: 0.13.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ash Furrow
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-05-16 00:00:00.000000000 Z
11
+ date: 2024-05-20 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: danger
@@ -192,7 +192,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
192
192
  - !ruby/object:Gem::Version
193
193
  version: '0'
194
194
  requirements: []
195
- rubygems_version: 3.3.13
195
+ rubygems_version: 3.4.10
196
196
  signing_key:
197
197
  specification_version: 4
198
198
  summary: A Danger plugin for running Ruby files through Rubocop.