inclusive-code 0.1.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 +7 -0
- data/README.md +1 -0
- data/config/default.yml +4 -0
- data/lib/inclusive-code.rb +11 -0
- data/lib/inclusive_code/cop/inclusive_code.rb +139 -0
- data/lib/inclusive_code/flexport.rb +14 -0
- data/lib/inclusive_code/flexport/inject.rb +20 -0
- data/lib/inclusive_code/flexport/version.rb +7 -0
- metadata +79 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 495cf47e5d18f5dad603026d5314bdfeeaeae01a045884444e2eb6316fc1ae68
|
4
|
+
data.tar.gz: 774b4de9e09abae3ec9c80373a9a6d3af1f63593df0ddd18a4922ad2deb92c46
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: c6cf747c66926af9c1bc9d6c7fcdc0cf52ee3ee9b3c20c17238989a5a2983c569bea8929dfcade6cf5f60333fb604a5a6ff961e8abc7d9b842c0692e4d07e281
|
7
|
+
data.tar.gz: e850f2f2cfae0a2ece5efd6368c8d8e45a3f31544aff933b4fccd802bcbb4bce420854472dc83c609c01aeaa6b1c8fc0a43060ebb52fa9a1af34897fc40aec88
|
data/README.md
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
## TODO: add usage instructions for rubocop gem
|
data/config/default.yml
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'rubocop'
|
4
|
+
|
5
|
+
require_relative 'inclusive_code/flexport'
|
6
|
+
require_relative 'inclusive_code/flexport/version'
|
7
|
+
require_relative 'inclusive_code/flexport/inject'
|
8
|
+
|
9
|
+
InclusiveCode::Flexport::Inject.defaults!
|
10
|
+
|
11
|
+
require_relative 'inclusive_code/cop/inclusive_code'
|
@@ -0,0 +1,139 @@
|
|
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
|
@@ -0,0 +1,14 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'inclusive_code/flexport/version'
|
4
|
+
|
5
|
+
module InclusiveCode
|
6
|
+
module Flexport
|
7
|
+
class Error < StandardError; end
|
8
|
+
PROJECT_ROOT = Pathname.new(__dir__).parent.parent.expand_path.freeze
|
9
|
+
CONFIG_DEFAULT = PROJECT_ROOT.join('config', 'default.yml').freeze
|
10
|
+
CONFIG = YAML.safe_load(CONFIG_DEFAULT.read).freeze
|
11
|
+
|
12
|
+
private_constant(:CONFIG_DEFAULT, :PROJECT_ROOT)
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# The original code is from https://github.com/rubocop-hq/rubocop-rspec/blob/master/lib/rubocop/rspec/inject.rb
|
4
|
+
# See https://github.com/rubocop-hq/rubocop-rspec/blob/master/MIT-LICENSE.md
|
5
|
+
module InclusiveCode
|
6
|
+
module Flexport
|
7
|
+
# Because RuboCop doesn't yet support plugins, we have to monkey patch in a
|
8
|
+
# bit of our configuration.
|
9
|
+
module Inject
|
10
|
+
def self.defaults!
|
11
|
+
path = CONFIG_DEFAULT.to_s
|
12
|
+
hash = ConfigLoader.send(:load_yaml_configuration, path)
|
13
|
+
config = Config.new(hash, path)
|
14
|
+
puts "configuration from #{path}" if ConfigLoader.debug?
|
15
|
+
config = ConfigLoader.merge_with_default(config, path)
|
16
|
+
ConfigLoader.instance_variable_set(:@default_configuration, config)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
metadata
ADDED
@@ -0,0 +1,79 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: inclusive-code
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Flexport Engineering
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2021-03-29 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: activesupport
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '4.0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '4.0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rubocop
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 0.70.0
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: 0.70.0
|
41
|
+
description: ''
|
42
|
+
email:
|
43
|
+
- dev@flexport.com
|
44
|
+
executables: []
|
45
|
+
extensions: []
|
46
|
+
extra_rdoc_files: []
|
47
|
+
files:
|
48
|
+
- README.md
|
49
|
+
- config/default.yml
|
50
|
+
- lib/inclusive-code.rb
|
51
|
+
- lib/inclusive_code/cop/inclusive_code.rb
|
52
|
+
- lib/inclusive_code/flexport.rb
|
53
|
+
- lib/inclusive_code/flexport/inject.rb
|
54
|
+
- lib/inclusive_code/flexport/version.rb
|
55
|
+
homepage: https://github.com/flexport/rubocop-flexport
|
56
|
+
licenses:
|
57
|
+
- MIT
|
58
|
+
metadata: {}
|
59
|
+
post_install_message:
|
60
|
+
rdoc_options: []
|
61
|
+
require_paths:
|
62
|
+
- lib
|
63
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
64
|
+
requirements:
|
65
|
+
- - ">="
|
66
|
+
- !ruby/object:Gem::Version
|
67
|
+
version: '2.4'
|
68
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
69
|
+
requirements:
|
70
|
+
- - ">="
|
71
|
+
- !ruby/object:Gem::Version
|
72
|
+
version: '0'
|
73
|
+
requirements: []
|
74
|
+
rubyforge_project:
|
75
|
+
rubygems_version: 2.7.7
|
76
|
+
signing_key:
|
77
|
+
specification_version: 4
|
78
|
+
summary: Inclusive Language RuboCop.
|
79
|
+
test_files: []
|