diff --git a/.circleci/config.yml b/.circleci/config.yml index 5ff347713..de302123f 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -81,9 +81,33 @@ jobs: # name: Brakeman # command: bundle exec brakeman - # - run: - # name: Stylelint - # command: yarn stylelint + - run: + name: Find Unused ESLint Rules + command: pnpm eslint_find_unused_rules + + - run: + name: ESLint + command: pnpm eslint + + - run: + name: Verify ESLint Autogen + command: bundle exec exe/eslint_autogen + + - run: + name: Vitest + command: pnpm vitest run --coverage + + - run: + name: Brakeman + command: bundle exec brakeman + + - run: + name: Stylelint + command: pnpm stylelint + + - run: + name: Verify Stylelint Autogen + command: bundle exec exe/stylelint_autogen - run: name: Rubocop diff --git a/.eslint_todo.ts b/.eslint_todo.ts new file mode 100644 index 000000000..4ad71d6e3 --- /dev/null +++ b/.eslint_todo.ts @@ -0,0 +1 @@ +export default [] \ No newline at end of file diff --git a/.stylelint_todo.yml b/.stylelint_todo.yml new file mode 100644 index 000000000..9c1c0899a --- /dev/null +++ b/.stylelint_todo.yml @@ -0,0 +1,16 @@ +# This configuration was generated by `exe/stylelint_autogen` +# on 2026-02-12 23:23:33 UTC. +# The point is for the user to remove these configuration records +# one by one as the offenses are removed from the code base. + +overrides: + + # Offense count: 9 + - rules: { 'order/properties-order': null } + files: + - app/assets/stylesheets/application.css + + # Offense count: 21 + - rules: { 'plugin/selector-bem-pattern': null } + files: + - app/assets/stylesheets/application.css diff --git a/exe/eslint_autogen b/exe/eslint_autogen new file mode 100755 index 000000000..af1b1059a --- /dev/null +++ b/exe/eslint_autogen @@ -0,0 +1,54 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +require "active_support/all" +require "stringio" + +TODO_FILE_PATH = "./.eslint_todo.ts" +HEADING = <<~COMMENTS.freeze + // This configuration was generated by `exe/eslint_autogen` + // on #{Time.now.utc}. + // The point is for the user to remove these configuration records + // one by one as the offenses are removed from the code base. +COMMENTS + +File.write(TODO_FILE_PATH, "export default []") +json = `pnpm --silent eslint --format json` +results = JSON.parse(json) + +by_rule = + results.each_with_object({}) do |result, hash| + result.fetch("messages").each do |message| + rule_id = message.fetch("ruleId") + next if rule_id.nil? + + hash[rule_id] ||= [] + hash[rule_id] << result.fetch("filePath") + end + end + +output = StringIO.new +output.puts(HEADING) +output.puts +output.puts("export default [") + +by_rule.sort.each do |rule, file_paths| + output.puts(" // Offense count: #{file_paths.length}") + output.puts(" {") + output.puts(" files: [") + + file_paths.uniq.sort.each do |file_path| + relative_path = file_path.sub("#{Dir.pwd}/", "") + output.puts(" \"#{relative_path}\",") + end + + output.puts(" ],") + output.puts(" rules: {") + output.puts(" \"#{rule}\": \"off\",") + output.puts(" },") + output.puts(" },") +end + +output.puts("];") + +File.write(TODO_FILE_PATH, output.string) diff --git a/exe/stylelint_autogen b/exe/stylelint_autogen new file mode 100755 index 000000000..cc2bdb9bb --- /dev/null +++ b/exe/stylelint_autogen @@ -0,0 +1,44 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +require "active_support/all" +require "stringio" + +TODO_FILE_PATH = "./.stylelint_todo.yml" +HEADING = <<~COMMENTS.freeze + # This configuration was generated by `exe/stylelint_autogen` + # on #{Time.now.utc}. + # The point is for the user to remove these configuration records + # one by one as the offenses are removed from the code base. +COMMENTS + +File.write(TODO_FILE_PATH, "{}") +json = + `pnpm --silent stylelint --quiet-deprecation-warnings --formatter json 2>&1` +results = JSON.parse(json) + +by_rule = + results.each_with_object({}) do |result, hash| + result.fetch("warnings").each do |warning| + hash[warning.fetch("rule")] ||= [] + hash[warning.fetch("rule")] << result.fetch("source") + end + end + +output = StringIO.new +output.puts(HEADING) +output.puts +output.puts("overrides:") + +by_rule.sort.each do |rule, file_paths| + output.puts + output.puts(" # Offense count: #{file_paths.length}") + output.puts(" - rules: { '#{rule}': null }") + output.puts(" files:") + + file_paths.uniq.sort.each do |file_path| + output.puts(" - #{file_path.sub("#{Dir.pwd}/", "")}") + end +end + +File.write(TODO_FILE_PATH, output.string)