Skip to content

Commit 8c944e5

Browse files
Introduce 'suspenders:lint` generator
Closes #1107 Closes #1143 Creates a holistic linting solution that covers JavaScript, CSS, Ruby and ERB. Introduces `eslint` and `stylelint` NPM commands that leverage [@thoughtbot/eslint-config][] and [@thoughtbot/stylelint-config][]. ``` yarn run eslint yarn run stylelint ``` Also introduces `.prettierrc` based off of our [Guides][]. [@thoughtbot/eslint-config]: https://github.com/thoughtbot/eslint-config [@thoughtbot/stylelint-config]: https://github.com/thoughtbot/stylelint-config [Guides]: https://github.com/thoughtbot/guides/blob/main/javascript/README.md#formatting Introduces `rake standard` which also runs `erblint` to lint ERB files via [better_html][], [erb_lint][] and [erblint-github][]. [better_html]: https://github.com/Shopify/better-html [erb_lint]: https://github.com/Shopify/erb-lint [erblint-github]: https://github.com/github/erblint-github A future commit will ensure these linting rules are run during CI. It should be noted that we deliberately permit this generator to be invoked on API only applications, because those applications can still contain views, like ones used for mailers. Also improves the developer experience by introducing `with_test_suite` helper, allowing the caller to run the generator in an application using minitest or RSpec.
1 parent 6bed72e commit 8c944e5

15 files changed

+473
-1
lines changed

NEWS.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ Unreleased
66
* Introduce `suspenders:factories` generator
77
* Introduce `suspenders:advisories` generator
88
* Introduce `suspenders:styles` generator
9+
* Introduce `suspenders:lint` generator
910

1011
20230113.0 (January, 13, 2023)
1112

README.md

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,30 @@ improvement for the viewer.
7272

7373
[inline_svg]: https://github.com/jamesmartin/inline_svg
7474

75+
### Lint
76+
77+
Creates a holistic linting solution that covers JavaScript, CSS, Ruby and ERB.
78+
79+
Introduces `eslint` and `stylelint` NPM commands that leverage
80+
[@thoughtbot/eslint-config][] and [@thoughtbot/stylelint-config][].
81+
82+
```
83+
yarn run eslint
84+
yarn run stylelint
85+
```
86+
87+
Also introduces `.prettierrc` based off of our [Guides][].
88+
89+
Introduces `rake standard` which also runs `erblint` to lint ERB files
90+
via [better_html][], [erb_lint][] and [erblint-github][].
91+
92+
[@thoughtbot/eslint-config]: https://github.com/thoughtbot/eslint-config
93+
[@thoughtbot/stylelint-config]: https://github.com/thoughtbot/stylelint-config
94+
[Guides]: https://github.com/thoughtbot/guides/blob/main/javascript/README.md#formatting
95+
[better_html]: https://github.com/Shopify/better-html
96+
[erb_lint]: https://github.com/Shopify/erb-lint
97+
[erblint-github]: https://github.com/github/erblint-github
98+
7599
### Styles
76100

77101
Configures applications to use [PostCSS][] or [Tailwind][] via
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
module Suspenders
2+
module Generators
3+
class LintGenerator < Rails::Generators::Base
4+
include Suspenders::Generators::Helpers
5+
6+
source_root File.expand_path("../../templates/lint", __FILE__)
7+
desc "Creates a holistic linting solution that covers JavaScript, CSS, Ruby and ERB."
8+
9+
def install_dependencies
10+
run "yarn add stylelint eslint @thoughtbot/stylelint-config @thoughtbot/eslint-config --dev"
11+
end
12+
13+
def install_gems
14+
gem_group :development, :test do
15+
gem "better_html", require: false
16+
gem "erb_lint", require: false
17+
gem "erblint-github", require: false
18+
gem "standard"
19+
end
20+
Bundler.with_unbundled_env { run "bundle install" }
21+
end
22+
23+
def configure_stylelint
24+
copy_file "stylelintrc.json", ".stylelintrc.json"
25+
end
26+
27+
def configure_eslint
28+
copy_file "eslintrc.json", ".eslintrc.json"
29+
end
30+
31+
def configure_prettier
32+
copy_file "prettierrc", ".prettierrc"
33+
end
34+
35+
def configure_erb_lint
36+
copy_file "erb-lint.yml", ".erb-lint.yml"
37+
copy_file "config_better_html.yml", "config/better_html.yml"
38+
copy_file "config_initializers_better_html.rb", "config/initializers/better_html.rb"
39+
copy_file "erblint.rake", "lib/tasks/erblint.rake"
40+
41+
if default_test_suite?
42+
copy_file "better_html_test.rb", "test/views/better_html_test.rb"
43+
elsif rspec_test_suite?
44+
copy_file "better_html_spec.rb", "spec/views/better_html_spec.rb"
45+
end
46+
end
47+
48+
# TODO: Consider extracting this into Rails
49+
def update_package_json
50+
content = File.read package_json
51+
json = JSON.parse content
52+
json["scripts"] ||= {}
53+
54+
json["scripts"]["eslint"] = "npx eslint 'app/javascript/**/*.js'"
55+
json["scripts"]["stylelint"] = "npx stylelint 'app/assets/stylesheets/**/*.css'"
56+
57+
File.write package_json, JSON.pretty_generate(json)
58+
end
59+
60+
private
61+
62+
def package_json
63+
Rails.root.join("package.json")
64+
end
65+
end
66+
end
67+
end
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
require "spec_helper"
2+
3+
describe "ERB Implementation" do
4+
def self.erb_lint
5+
configuration = ActiveSupport::ConfigurationFile.parse(".erb-lint.yml")
6+
7+
ActiveSupport::OrderedOptions.new.merge!(configuration.deep_symbolize_keys!)
8+
end
9+
10+
Rails.root.glob(erb_lint.glob).each do |template|
11+
it "raises html errors in #{template.relative_path_from(Rails.root)}" do
12+
validator = BetterHtml::BetterErb::ErubiImplementation.new(template.read)
13+
14+
validator.validate!
15+
end
16+
end
17+
end
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
require "test_helper"
2+
3+
class ErbImplementationTest < ActiveSupport::TestCase
4+
def self.erb_lint
5+
configuration = ActiveSupport::ConfigurationFile.parse(".erb-lint.yml")
6+
7+
ActiveSupport::OrderedOptions.new.merge!(configuration.deep_symbolize_keys!)
8+
end
9+
10+
Rails.root.glob(erb_lint.glob).each do |template|
11+
test "html errors in #{template.relative_path_from(Rails.root)}" do
12+
validator = BetterHtml::BetterErb::ErubiImplementation.new(template.read)
13+
14+
validator.validate!
15+
end
16+
end
17+
end
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
---
2+
allow_single_quoted_attributes: false
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
Rails.configuration.to_prepare do
2+
config = ActiveSupport::ConfigurationFile.parse("config/better_html.yml")
3+
4+
BetterHtml.config = BetterHtml::Config.new(config)
5+
end
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
---
2+
glob: "app/views/**/*.{html,turbo_stream}{+*,}.erb"
3+
4+
linters:
5+
AllowedScriptType:
6+
enabled: true
7+
allowed_types:
8+
- "module"
9+
- "text/javascript"
10+
ErbSafety:
11+
enabled: true
12+
better_html_config: "config/better_html.yml"
13+
GitHub::Accessibility::AvoidBothDisabledAndAriaDisabledCounter:
14+
enabled: true
15+
GitHub::Accessibility::AvoidGenericLinkTextCounter:
16+
enabled: true
17+
GitHub::Accessibility::DisabledAttributeCounter:
18+
enabled: true
19+
GitHub::Accessibility::IframeHasTitleCounter:
20+
enabled: true
21+
GitHub::Accessibility::ImageHasAltCounter:
22+
enabled: true
23+
GitHub::Accessibility::LandmarkHasLabelCounter:
24+
enabled: true
25+
GitHub::Accessibility::LinkHasHrefCounter:
26+
enabled: true
27+
GitHub::Accessibility::NestedInteractiveElementsCounter:
28+
enabled: true
29+
GitHub::Accessibility::NoAriaLabelMisuseCounter:
30+
enabled: true
31+
GitHub::Accessibility::NoPositiveTabIndexCounter:
32+
enabled: true
33+
GitHub::Accessibility::NoRedundantImageAltCounter:
34+
enabled: true
35+
GitHub::Accessibility::NoTitleAttributeCounter:
36+
enabled: true
37+
GitHub::Accessibility::SvgHasAccessibleTextCounter:
38+
enabled: true
39+
Rubocop:
40+
enabled: true
41+
rubocop_config:
42+
inherit_from:
43+
- .rubocop.yml
44+
45+
Lint/EmptyBlock:
46+
Enabled: false
47+
Layout/InitialIndentation:
48+
Enabled: false
49+
Layout/TrailingEmptyLines:
50+
Enabled: false
51+
Layout/TrailingWhitespace:
52+
Enabled: false
53+
Layout/LeadingEmptyLines:
54+
Enabled: false
55+
Style/FrozenStringLiteralComment:
56+
Enabled: false
57+
Style/MultilineTernaryOperator:
58+
Enabled: false
59+
Lint/UselessAssignment:
60+
Exclude:
61+
- "app/views/**/*"
62+
63+
EnableDefaultLinters: true
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
module ERBLint
2+
module RakeSupport
3+
# Allow command line flags set in STANDARDOPTS (like MiniTest's TESTOPTS)
4+
def self.argvify
5+
if ENV["ERBLINTOPTS"]
6+
ENV["ERBLINTOPTS"].split(/\s+/)
7+
else
8+
[]
9+
end
10+
end
11+
12+
# DELETE THIS FILE AFTER MERGE:
13+
#
14+
# * https://github.com/Shopify/better-html/pull/95
15+
#
16+
def self.backport!
17+
BetterHtml::TestHelper::SafeErb::AllowedScriptType::VALID_JAVASCRIPT_TAG_TYPES.push("module")
18+
end
19+
end
20+
end
21+
22+
desc "Lint templates with erb_lint"
23+
task "erblint" do
24+
require "erb_lint/cli"
25+
require "erblint-github/linters"
26+
27+
ERBLint::RakeSupport.backport!
28+
29+
cli = ERBLint::CLI.new
30+
success = cli.run(ERBLint::RakeSupport.argvify + ["--lint-all", "--format=compact"])
31+
fail unless success
32+
end
33+
34+
desc "Lint and automatically fix templates with erb_lint"
35+
task "erblint:autocorrect" do
36+
require "erb_lint/cli"
37+
require "erblint-github/linters"
38+
39+
ERBLint::RakeSupport.backport!
40+
41+
cli = ERBLint::CLI.new
42+
success = cli.run(ERBLint::RakeSupport.argvify + ["--lint-all", "--autocorrect"])
43+
fail unless success
44+
end
45+
46+
task "standard" => "erblint"
47+
task "standard:fix" => "erblint:autocorrect"
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"extends": [
3+
"@thoughtbot/eslint-config/base",
4+
"@thoughtbot/eslint-config/prettier"
5+
]
6+
}

0 commit comments

Comments
 (0)