inclusive-code 0.1.0 → 0.1.5
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 +4 -4
- data/README.md +120 -1
- data/config/default.yml +2 -0
- data/lib/inclusive-code.rb +5 -5
- data/lib/rubocop/cop/inclusive_code.rb +174 -0
- data/lib/{inclusive_code/flexport.rb → rubocop/inclusive_code.rb} +4 -4
- data/lib/{inclusive_code/flexport → rubocop/inclusive_code}/inject.rb +3 -3
- data/lib/rubocop/inclusive_code/version.rb +7 -0
- metadata +9 -9
- data/lib/inclusive_code/cop/inclusive_code.rb +0 -139
- data/lib/inclusive_code/flexport/version.rb +0 -7
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 7fc5f8f716dd932818068d5b282bc52d1f7e93ad38eec1f16e100076347c42f8
|
4
|
+
data.tar.gz: 372a8b68115654589caf6ec9a6c03e94c96d5f2cced613065c15b0454214076a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 6781133cb6fad6ef8bb373de9d7359a0d121901f58863adcf5f0498c0550059af3b3b6787cab808ca104392ef790e22e6fe4529805362c5c0c3c5332269004d5
|
7
|
+
data.tar.gz: aa2a97adf00529dbdf3fb6f525aaaa994e7988fe0242d47aeb934cd063fc448e0c8723ee22f91da792727c914b68b6fbbea78142fe271641e3242818450d64da
|
data/README.md
CHANGED
@@ -1 +1,120 @@
|
|
1
|
-
##
|
1
|
+
## Installation
|
2
|
+
|
3
|
+
Add this line to your application's Gemfile:
|
4
|
+
|
5
|
+
```ruby
|
6
|
+
gem 'inclusive-code'
|
7
|
+
```
|
8
|
+
|
9
|
+
And then execute:
|
10
|
+
|
11
|
+
$ bundle
|
12
|
+
|
13
|
+
Or install it yourself as:
|
14
|
+
|
15
|
+
$ gem install inclusive-code
|
16
|
+
|
17
|
+
## Usage
|
18
|
+
Configure your own set of flagged terms following the structure of the `inclusive_code_flagged_terms.yml` file at the top level of this repo. We recommend storing it in `'app/constants/inclusive_code/inclusive_code_flagged_terms.yml'`.
|
19
|
+
|
20
|
+
Put this into your .rubocop.yml:
|
21
|
+
|
22
|
+
```yaml
|
23
|
+
require:
|
24
|
+
- inclusive-code
|
25
|
+
|
26
|
+
Flexport/InclusiveCode:
|
27
|
+
Enabled: true
|
28
|
+
GlobalConfigPath: 'app/constants/inclusive_code/inclusive_code_flagged_terms.yml' # or your path
|
29
|
+
DisableAutoCorrect: false
|
30
|
+
```
|
31
|
+
|
32
|
+
You can run the cop on your entire codebase with `rubocop --only Flexport/InclusiveCode`.
|
33
|
+
|
34
|
+
You can run the cop on a specific file with `rubocop --only Flexport/InclusiveCode file_path`.
|
35
|
+
|
36
|
+
If you want to add inline `rubocop:disable` or `rubocop:todo` comments on all offenses, set `DisableAutoCorrect: true` in your .rubocop.yml, and run `rubocop --only Flexport/InclusiveCode --auto-correct --disable-uncorrectable`.
|
37
|
+
|
38
|
+
## Configuration
|
39
|
+
|
40
|
+
The inclusive-code gem includes an initial configuration to get your project started. You can customize this configuration to fit your needs.
|
41
|
+
|
42
|
+
### Flagging harmful terms
|
43
|
+
|
44
|
+
Rules are added under the key `flagged_term` as follows:
|
45
|
+
|
46
|
+
```yaml
|
47
|
+
---
|
48
|
+
flagged_terms:
|
49
|
+
some_harmful_term: {}
|
50
|
+
```
|
51
|
+
|
52
|
+
### Specifying harmful terminology
|
53
|
+
|
54
|
+
You can specify harmful terminology using basic keys (`another_harmful_term:`), string keys (`" his ":`), or using [regular expressions](https://rubular.com/r/HvcomHUBZ3KFCz) like `"white[-_\\s]*list":`. Please note that when specifying a regular expression, some characters (`\`) may need to be escaped. Examples:
|
55
|
+
|
56
|
+
```yaml
|
57
|
+
---
|
58
|
+
flagged_terms:
|
59
|
+
"white[-_\\s]*list": {}
|
60
|
+
"black[-_\\s]*list": {}
|
61
|
+
" him ": {}
|
62
|
+
master: {}
|
63
|
+
```
|
64
|
+
|
65
|
+
### Suggestions and Autocorrect
|
66
|
+
|
67
|
+
In a document titled [Terminology, Power and Offensive Language](https://tools.ietf.org/id/draft-knodel-terminology-01.html), the Internet Engineering Task Force (IETF) recommends that an editor or reviewer *should* "offer alternatives for offensive terminology as an important act of correcting larger editorial issues and clarifying technical concepts."
|
68
|
+
|
69
|
+
As this gem does some of the work of an editor or reviewer, it is appropriate that it should allow for communicating better alternatives when it finds harmful technology.
|
70
|
+
|
71
|
+
Here's how you can offer alternative suggestions:
|
72
|
+
|
73
|
+
```yaml
|
74
|
+
---
|
75
|
+
flagged_terms:
|
76
|
+
some_harmful_term:
|
77
|
+
suggestions:
|
78
|
+
- some_thoughtful_alternative
|
79
|
+
- some_other_thoughtful_alternative
|
80
|
+
```
|
81
|
+
|
82
|
+
When using autocorrect, the first item in the suggestions array will be used as the autocorrect term.
|
83
|
+
|
84
|
+
### Allowing exceptions
|
85
|
+
|
86
|
+
This gem supports two ways to specify exceptions to your rules: allowing specific terms, allowing specific files.
|
87
|
+
|
88
|
+
#### Allowing specific terms
|
89
|
+
|
90
|
+
You might want to do this to allow for an [Industry Term Exception](../README.md#industry-term-exemption). Here's how to allow certain terms using the `allowed:` key:
|
91
|
+
|
92
|
+
```yaml
|
93
|
+
---
|
94
|
+
flagged_terms:
|
95
|
+
master:
|
96
|
+
suggestions:
|
97
|
+
- main
|
98
|
+
allowed:
|
99
|
+
- master bill
|
100
|
+
- master air waybill
|
101
|
+
- master consol
|
102
|
+
- master shipment
|
103
|
+
```
|
104
|
+
|
105
|
+
#### Allowing specific files
|
106
|
+
|
107
|
+
You might want to do this when you wish to disallow some term, but you need to allow it in certain files. Perhaps you rely on some library which requires you to configure it using some harmful terminology. Here's how to allow occurrences of a harmful term when they occur within some file using the `allowed_files:` key:
|
108
|
+
|
109
|
+
```yaml
|
110
|
+
---
|
111
|
+
flagged_terms:
|
112
|
+
whitelist:
|
113
|
+
suggestions:
|
114
|
+
- allowlist
|
115
|
+
allowed_files:
|
116
|
+
- config/initializers/some_gem_config.rb
|
117
|
+
- .some_gem/*
|
118
|
+
```
|
119
|
+
|
120
|
+
This will result in allowing offenses in any files returned by `Dir.glob("{config/initializers/some_gem_config.rb,.some_gem/*}")`
|
data/config/default.yml
CHANGED
data/lib/inclusive-code.rb
CHANGED
@@ -2,10 +2,10 @@
|
|
2
2
|
|
3
3
|
require 'rubocop'
|
4
4
|
|
5
|
-
require_relative 'inclusive_code
|
6
|
-
require_relative 'inclusive_code/
|
7
|
-
require_relative 'inclusive_code/
|
5
|
+
require_relative 'rubocop/inclusive_code'
|
6
|
+
require_relative 'rubocop/inclusive_code/version'
|
7
|
+
require_relative 'rubocop/inclusive_code/inject'
|
8
8
|
|
9
|
-
InclusiveCode::
|
9
|
+
RuboCop::InclusiveCode::Inject.defaults!
|
10
10
|
|
11
|
-
require_relative '
|
11
|
+
require_relative 'rubocop/cop/inclusive_code'
|
@@ -0,0 +1,174 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'active_support/core_ext/string'
|
4
|
+
|
5
|
+
module RuboCop
|
6
|
+
module Cop
|
7
|
+
module Flexport
|
8
|
+
# This cop encourages use of inclusive language to help programmers avoid
|
9
|
+
# using terminology that is derogatory, hurtful, or perpetuates discrimination,
|
10
|
+
# either directly or indirectly, in their code.
|
11
|
+
#
|
12
|
+
# @example
|
13
|
+
#
|
14
|
+
# # bad
|
15
|
+
#
|
16
|
+
# BLACKLIST_COUNTRIES = "blacklist_countries".freeze
|
17
|
+
#
|
18
|
+
# @example
|
19
|
+
#
|
20
|
+
# # good
|
21
|
+
#
|
22
|
+
# BLOCKLIST_COUNTRIES = "blocklist_countries".freeze
|
23
|
+
#
|
24
|
+
#
|
25
|
+
class InclusiveCode < Cop
|
26
|
+
include RangeHelp
|
27
|
+
|
28
|
+
SEVERITY = 'warning'
|
29
|
+
|
30
|
+
FLAG_ONLY_MSG = '🚫 Use of non_inclusive word: `%<non_inclusive_word>s`.'
|
31
|
+
FULL_FLAG_MSG = "#{FLAG_ONLY_MSG} Consider using these suggested alternatives: `%<suggestions>s`."
|
32
|
+
|
33
|
+
ALLOWED_TERM_MASK_CHAR = '*'.freeze
|
34
|
+
|
35
|
+
def initialize(config = nil, options = nil, source_file = nil)
|
36
|
+
super(config, options)
|
37
|
+
|
38
|
+
source_file ||= YAML.load_file(cop_config['GlobalConfigPath'])
|
39
|
+
@non_inclusive_words_alternatives_hash = source_file['flagged_terms']
|
40
|
+
@all_non_inclusive_words = @non_inclusive_words_alternatives_hash.keys
|
41
|
+
@non_inclusive_words_regex = concatenated_regex(@all_non_inclusive_words)
|
42
|
+
@allowed_terms = {}
|
43
|
+
@allowed_files = {}
|
44
|
+
|
45
|
+
@all_non_inclusive_words.each do |word|
|
46
|
+
@allowed_terms[word] = get_allowed_string(word)
|
47
|
+
@allowed_files[word] = source_file['flagged_terms'][word]['allowed_files'] || []
|
48
|
+
end
|
49
|
+
@allowed_regex = @allowed_terms.values.reject(&:blank?).join('|')
|
50
|
+
|
51
|
+
@allowed_regex = if @allowed_regex.blank?
|
52
|
+
Regexp.new(/^$/)
|
53
|
+
else
|
54
|
+
Regexp.new(@allowed_regex, Regexp::IGNORECASE)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def investigate(processed_source)
|
59
|
+
non_inclusive_words_for_current_file = @all_non_inclusive_words.reject do |non_inclusive_word|
|
60
|
+
Dir.glob("{#{@allowed_files[non_inclusive_word].join(',')}}").include?(processed_source.path)
|
61
|
+
end
|
62
|
+
|
63
|
+
processed_source.lines.each_with_index do |line, line_number|
|
64
|
+
next unless line.match(@non_inclusive_words_regex)
|
65
|
+
|
66
|
+
non_inclusive_words_for_current_file.each do |non_inclusive_word|
|
67
|
+
allowed = @allowed_terms[non_inclusive_word]
|
68
|
+
scan_regex = /(?=#{non_inclusive_word})/i
|
69
|
+
if allowed.present?
|
70
|
+
line = line.gsub(/(#{allowed})/i){ |match| ALLOWED_TERM_MASK_CHAR * match.size }
|
71
|
+
end
|
72
|
+
locations = line.enum_for(
|
73
|
+
:scan,
|
74
|
+
scan_regex
|
75
|
+
).map { Regexp.last_match&.offset(0)&.first }
|
76
|
+
|
77
|
+
non_inclusive_words = line.scan(/#{non_inclusive_word}/i)
|
78
|
+
|
79
|
+
locations = locations.zip(non_inclusive_words).to_h
|
80
|
+
next if locations.blank?
|
81
|
+
|
82
|
+
locations.each do |location, word|
|
83
|
+
range = source_range(
|
84
|
+
processed_source.buffer,
|
85
|
+
line_number + 1,
|
86
|
+
location,
|
87
|
+
word.length
|
88
|
+
)
|
89
|
+
add_offense(
|
90
|
+
range,
|
91
|
+
location: range,
|
92
|
+
message: create_message(word),
|
93
|
+
severity: SEVERITY
|
94
|
+
)
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
# Also error for non-inclusive language in file names
|
99
|
+
path = processed_source.path
|
100
|
+
return if path.nil?
|
101
|
+
|
102
|
+
non_inclusive_words_match = path.match(concatenated_regex(non_inclusive_words_for_current_file))
|
103
|
+
return unless non_inclusive_words_match && !path.match(@allowed_regex)
|
104
|
+
|
105
|
+
range = source_range(processed_source.buffer, 1, 0)
|
106
|
+
add_offense(
|
107
|
+
range,
|
108
|
+
location: range,
|
109
|
+
message: create_message(non_inclusive_words_match[0]),
|
110
|
+
severity: SEVERITY
|
111
|
+
)
|
112
|
+
end
|
113
|
+
|
114
|
+
def autocorrect(arg_pair)
|
115
|
+
return if cop_config['DisableAutoCorrect']
|
116
|
+
|
117
|
+
word_to_correct = arg_pair.source
|
118
|
+
correction = correction_for_word(word_to_correct)
|
119
|
+
return if correction['suggestions'].blank?
|
120
|
+
|
121
|
+
corrected = correction['suggestions'][0]
|
122
|
+
|
123
|
+
# Only respects case if it is capitalized or uniform (all upper or all lower)
|
124
|
+
to_upcase = word_to_correct == word_to_correct.upcase
|
125
|
+
if to_upcase
|
126
|
+
corrected = corrected.upcase
|
127
|
+
elsif word_to_correct == word_to_correct.capitalize
|
128
|
+
corrected = corrected.capitalize
|
129
|
+
end
|
130
|
+
|
131
|
+
lambda do |corrector|
|
132
|
+
corrector.insert_before(arg_pair, corrected)
|
133
|
+
corrector.remove(arg_pair)
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
private
|
138
|
+
|
139
|
+
def concatenated_regex(non_inclusive_words)
|
140
|
+
Regexp.new(
|
141
|
+
non_inclusive_words.join('|'),
|
142
|
+
Regexp::IGNORECASE
|
143
|
+
)
|
144
|
+
end
|
145
|
+
|
146
|
+
def get_allowed_string(non_inclusive_word)
|
147
|
+
allowed = @non_inclusive_words_alternatives_hash[non_inclusive_word].fetch('allowed') { [] }
|
148
|
+
snake_case = allowed.map { |e| e.tr(' ', '_').underscore }
|
149
|
+
pascal_case = snake_case.map(&:camelize)
|
150
|
+
(allowed + snake_case + pascal_case).join('|')
|
151
|
+
end
|
152
|
+
|
153
|
+
def correction_for_word(word_to_correct)
|
154
|
+
_, correction = @non_inclusive_words_alternatives_hash.detect do |correction_key, _|
|
155
|
+
Regexp.new(correction_key, Regexp::IGNORECASE).match?(word_to_correct.downcase)
|
156
|
+
end
|
157
|
+
|
158
|
+
correction || {}
|
159
|
+
end
|
160
|
+
|
161
|
+
def create_message(non_inclusive_word)
|
162
|
+
correction = correction_for_word(non_inclusive_word)
|
163
|
+
suggestions = correction.fetch('suggestions') { [] }.join(', ')
|
164
|
+
|
165
|
+
format(
|
166
|
+
suggestions.present? ? FULL_FLAG_MSG : FLAG_ONLY_MSG,
|
167
|
+
non_inclusive_word: non_inclusive_word,
|
168
|
+
suggestions: suggestions
|
169
|
+
)
|
170
|
+
end
|
171
|
+
end
|
172
|
+
end
|
173
|
+
end
|
174
|
+
end
|
@@ -1,9 +1,9 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require 'inclusive_code/
|
3
|
+
require 'rubocop/inclusive_code/version'
|
4
4
|
|
5
|
-
module
|
6
|
-
module
|
5
|
+
module RuboCop
|
6
|
+
module InclusiveCode
|
7
7
|
class Error < StandardError; end
|
8
8
|
PROJECT_ROOT = Pathname.new(__dir__).parent.parent.expand_path.freeze
|
9
9
|
CONFIG_DEFAULT = PROJECT_ROOT.join('config', 'default.yml').freeze
|
@@ -11,4 +11,4 @@ module InclusiveCode
|
|
11
11
|
|
12
12
|
private_constant(:CONFIG_DEFAULT, :PROJECT_ROOT)
|
13
13
|
end
|
14
|
-
end
|
14
|
+
end
|
@@ -2,8 +2,8 @@
|
|
2
2
|
|
3
3
|
# The original code is from https://github.com/rubocop-hq/rubocop-rspec/blob/master/lib/rubocop/rspec/inject.rb
|
4
4
|
# See https://github.com/rubocop-hq/rubocop-rspec/blob/master/MIT-LICENSE.md
|
5
|
-
module
|
6
|
-
module
|
5
|
+
module RuboCop
|
6
|
+
module InclusiveCode
|
7
7
|
# Because RuboCop doesn't yet support plugins, we have to monkey patch in a
|
8
8
|
# bit of our configuration.
|
9
9
|
module Inject
|
@@ -17,4 +17,4 @@ module InclusiveCode
|
|
17
17
|
end
|
18
18
|
end
|
19
19
|
end
|
20
|
-
end
|
20
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: inclusive-code
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.5
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Flexport Engineering
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2021-
|
11
|
+
date: 2021-05-26 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activesupport
|
@@ -48,14 +48,15 @@ files:
|
|
48
48
|
- README.md
|
49
49
|
- config/default.yml
|
50
50
|
- lib/inclusive-code.rb
|
51
|
-
- lib/
|
52
|
-
- lib/inclusive_code
|
53
|
-
- lib/inclusive_code/
|
54
|
-
- lib/inclusive_code/
|
51
|
+
- lib/rubocop/cop/inclusive_code.rb
|
52
|
+
- lib/rubocop/inclusive_code.rb
|
53
|
+
- lib/rubocop/inclusive_code/inject.rb
|
54
|
+
- lib/rubocop/inclusive_code/version.rb
|
55
55
|
homepage: https://github.com/flexport/rubocop-flexport
|
56
56
|
licenses:
|
57
57
|
- MIT
|
58
|
-
metadata:
|
58
|
+
metadata:
|
59
|
+
source_code_uri: https://github.com/flexport/inclusive-code
|
59
60
|
post_install_message:
|
60
61
|
rdoc_options: []
|
61
62
|
require_paths:
|
@@ -71,8 +72,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
71
72
|
- !ruby/object:Gem::Version
|
72
73
|
version: '0'
|
73
74
|
requirements: []
|
74
|
-
|
75
|
-
rubygems_version: 2.7.7
|
75
|
+
rubygems_version: 3.1.4
|
76
76
|
signing_key:
|
77
77
|
specification_version: 4
|
78
78
|
summary: Inclusive Language RuboCop.
|
@@ -1,139 +0,0 @@
|
|
1
|
-
module InclusiveCode
|
2
|
-
module Cop
|
3
|
-
module Flexport
|
4
|
-
# This cop encourages use of inclusive language to help programmers avoid
|
5
|
-
# using terminology that is derogatory, hurtful, or perpetuates discrimination,
|
6
|
-
# either directly or indirectly, in their code.
|
7
|
-
#
|
8
|
-
# @example
|
9
|
-
#
|
10
|
-
# # bad
|
11
|
-
#
|
12
|
-
# BLACKLIST_COUNTRIES = "blacklist_countries".freeze
|
13
|
-
#
|
14
|
-
# @example
|
15
|
-
#
|
16
|
-
# # good
|
17
|
-
#
|
18
|
-
# BLOCKLIST_COUNTRIES = "blocklist_countries".freeze
|
19
|
-
#
|
20
|
-
#
|
21
|
-
class InclusiveCode < Cop
|
22
|
-
include RangeHelp
|
23
|
-
|
24
|
-
SEVERITY = "warning".freeze
|
25
|
-
|
26
|
-
MSG = "🚫 Use of non_inclusive word: `%<non_inclusive_word>s`. Consider using these suggested alternatives: `%<suggestions>s`.".freeze
|
27
|
-
|
28
|
-
NON_INCLUSIVE_WORDS_ALTERNATIVES_HASH = YAML.load_file(global_config_path)["flagged_terms"]
|
29
|
-
|
30
|
-
ALL_NON_INCLUSIVE_WORDS = NON_INCLUSIVE_WORDS_ALTERNATIVES_HASH.keys
|
31
|
-
|
32
|
-
NON_INCLUSIVE_WORDS_REGEX = Regexp.new(
|
33
|
-
ALL_NON_INCLUSIVE_WORDS.join("|"),
|
34
|
-
Regexp::IGNORECASE,
|
35
|
-
)
|
36
|
-
|
37
|
-
def self.get_allowed_string(non_inclusive_word)
|
38
|
-
allowed = NON_INCLUSIVE_WORDS_ALTERNATIVES_HASH[non_inclusive_word]["allowed"]
|
39
|
-
snake_case = allowed.map { |e| e.tr(" ", "_").underscore }
|
40
|
-
pascal_case = snake_case.map(&:camelize)
|
41
|
-
(allowed + snake_case + pascal_case).join("|")
|
42
|
-
end
|
43
|
-
|
44
|
-
ALLOWED = ALL_NON_INCLUSIVE_WORDS.collect do |word|
|
45
|
-
[
|
46
|
-
word,
|
47
|
-
get_allowed_string(word),
|
48
|
-
]
|
49
|
-
end.to_h
|
50
|
-
|
51
|
-
ALLOWED_REGEX = Regexp.new(ALLOWED.values.reject(&:blank?).join("|"), Regexp::IGNORECASE)
|
52
|
-
|
53
|
-
def investigate(processed_source)
|
54
|
-
processed_source.lines.each_with_index do |line, line_number|
|
55
|
-
if line.match(NON_INCLUSIVE_WORDS_REGEX)
|
56
|
-
ALL_NON_INCLUSIVE_WORDS.each do |non_inclusive_word|
|
57
|
-
allowed = ALLOWED[non_inclusive_word]
|
58
|
-
if allowed.blank?
|
59
|
-
scan_regex = /(?=#{non_inclusive_word})/i
|
60
|
-
else
|
61
|
-
scan_regex = /(?=#{non_inclusive_word})(?!(#{ALLOWED[non_inclusive_word]}))/i
|
62
|
-
end
|
63
|
-
locations = line.enum_for(
|
64
|
-
:scan,
|
65
|
-
scan_regex,
|
66
|
-
).map { Regexp.last_match&.offset(0)&.first }
|
67
|
-
next if locations.blank?
|
68
|
-
locations.each do |location|
|
69
|
-
range = source_range(
|
70
|
-
processed_source.buffer,
|
71
|
-
line_number + 1,
|
72
|
-
location,
|
73
|
-
non_inclusive_word.length,
|
74
|
-
)
|
75
|
-
add_offense(
|
76
|
-
range,
|
77
|
-
location: range,
|
78
|
-
message: create_message(non_inclusive_word),
|
79
|
-
severity: SEVERITY,
|
80
|
-
)
|
81
|
-
end
|
82
|
-
end
|
83
|
-
end
|
84
|
-
end
|
85
|
-
# Also error for non-inclusive language in file names
|
86
|
-
path = processed_source.path
|
87
|
-
if path.nil?
|
88
|
-
return
|
89
|
-
end
|
90
|
-
non_inclusive_words_match = path.match(NON_INCLUSIVE_WORDS_REGEX)
|
91
|
-
if non_inclusive_words_match && !path.match(ALLOWED_REGEX)
|
92
|
-
range = source_range(processed_source.buffer, 1, 0)
|
93
|
-
add_offense(
|
94
|
-
range,
|
95
|
-
location: range,
|
96
|
-
message: create_message(non_inclusive_words_match[0]),
|
97
|
-
severity: SEVERITY,
|
98
|
-
)
|
99
|
-
end
|
100
|
-
end
|
101
|
-
|
102
|
-
def autocorrect(arg_pair)
|
103
|
-
word_to_correct = arg_pair.source
|
104
|
-
word_to_correct_downcase = word_to_correct.downcase
|
105
|
-
return if !NON_INCLUSIVE_WORDS_ALTERNATIVES_HASH.key?(word_to_correct_downcase)
|
106
|
-
return if NON_INCLUSIVE_WORDS_ALTERNATIVES_HASH[word_to_correct_downcase]["suggestions"].blank?
|
107
|
-
corrected = NON_INCLUSIVE_WORDS_ALTERNATIVES_HASH[word_to_correct_downcase]["suggestions"][0]
|
108
|
-
|
109
|
-
# Only respects case if it is capitalized or uniform (all upper or all lower)
|
110
|
-
to_upcase = word_to_correct == word_to_correct.upcase
|
111
|
-
if to_upcase
|
112
|
-
corrected = corrected.upcase
|
113
|
-
elsif word_to_correct == word_to_correct.capitalize
|
114
|
-
corrected = corrected.capitalize
|
115
|
-
end
|
116
|
-
|
117
|
-
->(corrector) do
|
118
|
-
corrector.insert_before(arg_pair, corrected)
|
119
|
-
corrector.remove(arg_pair)
|
120
|
-
end
|
121
|
-
end
|
122
|
-
|
123
|
-
private
|
124
|
-
|
125
|
-
def create_message(non_inclusive_word)
|
126
|
-
format(
|
127
|
-
MSG,
|
128
|
-
non_inclusive_word: non_inclusive_word,
|
129
|
-
suggestions: NON_INCLUSIVE_WORDS_ALTERNATIVES_HASH[non_inclusive_word]["suggestions"].join(", "),
|
130
|
-
)
|
131
|
-
end
|
132
|
-
|
133
|
-
def global_config_path
|
134
|
-
cop_config['GlobalConfigPath']
|
135
|
-
end
|
136
|
-
end
|
137
|
-
end
|
138
|
-
end
|
139
|
-
end
|