[RUBY] Faisons un custom_cop qui souligne le tremblement du nom

Introduction: cat2:

Fanclub et fan_club sont mélangés dans le code ... Je pense que c'est courant. J'aimerais l'unifier à ceux qui disent que c'est celui de l'équipe, mais je n'ai pas assez de mémoire dans ma tête pour me souvenir de telles règles ... C'est aussi une bonne idée de faire remarquer les gens, et j'aimerais laisser cela à un ordinateur qui le souligne patiemment à plusieurs reprises. J'ai donc créé un custom_cop pour Rubocop.

Source ici.

Au fait, même si vous n'utilisez pas un travail aussi gênant, grep au diff avec la branche master semble être plus facile. Mais je voulais écrire custom_cop. C'est un programme du dimanche, non?

Spécifications simples: gâteau:

Si vous essayez de définir ce que vous voulez pour autant que vous puissiez comprendre ... Est-ce que c'est comme ça? Les mots clés augmentent plusieurs fois pendant le développement de l'application, je souhaite donc les définir dans un fichier externe.

Répertoriez les mauvais mots et les mots que vous souhaitez corriger dans le fichier YAML, indiquez les mauvais mots et corrigez-les.

Essayez de le faire: muscle:

Quoi qu'il en soit, ce n'est pas intéressant si cela ne fonctionne pas, donc je ferai de mon mieux pour viser: point_down:.

Faites remarquer si la valeur que vous mettez dans la variable est le mauvais mot fanclub

Vérifiez le code cible: dart:

Préparez du code qui peut être vérifié lors de l'exécution de rubocop. Je pense que le code de l'application que vous utilisez est correct.

target.rb


a = 'fanclub'

custom_cop :cop:

Je l'ai fait presque en copiant. Quoi qu'il en soit, j'espère que vous pourrez vous déplacer comme ça.

lib/custom_cops/spell_inconsistency.rb


# frozen_string_literal: true

module CustomCops
  class SpellInconsistency < RuboCop::Cop::Cop
    WRONG_KEYWORD = 'fanclub'.freeze

    def on_str(node)
      add_offense(node, message: "Use 'fan_club' instead of 'fanclub'.") if node.source.include?(WRONG_KEYWORD)
    end
  end
end

.rubocop.yml :wrench:

Définissez le custom_cop créé pour qu'il puisse être utilisé par rubocop.

yaml:.rubocop.yml


require:
  - './lib/custom_cops/spell_inconsistency'

Exécuter: lever_hands:

Lorsqu'il est exécuté avec rubocop target.rb ...

image.png

En plus de signaler «Je n'utilise pas de variables uniquement pour la définition» ou «Parce que c'est une constante, veuillez la figer» ... «CustomCops / SpellInconsistency: Utilisez« fan_club »au lieu de« fanclub ». !!

D'autres mots sont également pris en charge: zap:

Je voudrais l'étendre à celui enregistré dans YAML.

Faites remarquer si la valeur que vous mettez dans la variable est le mauvais mot dans le fichier YAML

spell_inconsistency.yml :wrench:

Ici, enregistrez fanclub, Fanclub et FANCLUB. À l'avenir, j'aimerais les écrire en deux mots, respectivement fan_club, FanClub et FAN_CLUB.

lib/custom_cops/spell_inconsistency.yml


# Wrong: Correct
fanclub: fan_club
Fanclub: Fanclub
FANCLUB: FAN_CLUB

Vérifiez le code cible: dart:

target.rb


a = 'fanclub'
b = 'Fanclub'
c = 'FANCLUB'

custom_cop :cop:

Le fichier dans lequel le mot est enregistré est lu par YAML.load_file, et il est vérifié en le tournant par ʻeach`.

lib/custom_cops/spell_inconsistency.rb


# frozen_string_literal: true

require 'yaml'

module CustomCops
  class SpellInconsistency < RuboCop::Cop::Cop
    MESSAGE_TEMPLATE = "Use '%s' instead of '%s'."
    SPELL_INCONSISTENCIES = YAML.load_file(Pathname(__dir__).join('spell_inconsistency.yml'))

    def on_str(node)
      SPELL_INCONSISTENCIES.each do |wrong_keyword, correct_keyword|
        add_offense(node, message: message(wrong_keyword, correct_keyword)) if node.source.include?(wrong_keyword)
      end
    end

    private

    def message(wrong_keyword, correct_keyword)
      MESSAGE_TEMPLATE % [correct_keyword, wrong_keyword]
    end
  end
end

Exécuter: lever_hands:

Si vous l'exécutez avec rubocop target.rb, il y a beaucoup de messages supplémentaires, donc si vous spécifiez uniquement votre propre custom_cop comme rubocop --only CustomCops / SpellInconsistency target.rb et exécutez-le ...

image.png

Oh, ça a l'air bien: pouce levé:

Remplacer les symboles et les constantes

Faites de même lorsque vous remplacez des symboles ou des constantes qui ressemblent beaucoup à une chaîne.

Vérifiez le code cible: dart:

target.rb


a = 'Fanclub'
b = :fanclub
c = FANCLUB

custom_cop :cop:

Avant d'écrire, jetez un œil à RuboCop :: AST :: Traversal.

Immédiatement après avoir vu # walk On dirait que vous appelez une méthode par ce nom, en regardant le type de nœud que vous avez traversé. Il semble donc que mon # on_str ait également été appelé.

lib/rubocop/ast/traversal.rb


def walk(node)
  return if node.nil?

  send(:"on_#{node.type}", node)
  nil
end

J'ai cherché partout dans le fichier et j'ai trouvé const et sym qui lui ressemblaient, alors j'ai décidé d'implémenter # on_sym et on_const également. La méthode d'inspection est exactement la même, alors définissez-la avec define_method.

lib/custom_cops/spell_inconsistency.rb


# frozen_string_literal: true

require 'yaml'

module CustomCops
  class SpellInconsistency < RuboCop::Cop::Cop
    MESSAGE_TEMPLATE = "Use '%s' instead of '%s'."
    SPELL_INCONSISTENCIES = YAML.load_file(Pathname(__dir__).join('spell_inconsistency.yml'))

    NODE_TYPES = %I[str const sym].freeze
    NODE_TYPES.each do |node_type|
      define_method "on_#{node_type}" do |node|
        SPELL_INCONSISTENCIES.each do |wrong_keyword, correct_keyword|
          add_offense(node, message: message(wrong_keyword, correct_keyword)) if node.source.include?(wrong_keyword)
        end
      end
    end

    def message(wrong_keyword, correct_keyword)
      MESSAGE_TEMPLATE % [correct_keyword, wrong_keyword]
    end
  end
end

Exécuter: lever_hands:

Lorsqu'il est exécuté en tant que rubocop --only CustomCops / SpellInconsistency target.rb ...

image.png

Ça a l'air bien: +1:

Faites remarquer si vous utilisez le mauvais mot dans le nom de la variable

Maintenant que nous avons des chaînes, des symboles et des constantes, faisons-leur remarquer si nous utilisons le mauvais mot pour le nom de la variable.

Vérifiez le code cible: dart:

target.rb


a = 'Fanclub'
b = :fanclub
c = FANCLUB
fanclub = 'a'

ruby-parse partie 1

En parlant de variables, variable serait var ... Donc, [ RuboCop :: AST :: Traversal](https://github.com/rubocop-hq/rubocop-ast/blob/master/lib J'ai jeté un coup d'œil à /rubocop/ast/traversal.rb), mais ... il n'y en a pas beaucoup, et il y en a beaucoup ...

En regardant Development Basic de la documentation officielle RuboCop, il est dit d'utiliser ruby-parse sur la ligne de commande. Je comprends.

image.png

C'était «lvasgn». Ajouter à NODE_TYPES ...

lib/custom_cops/spell_inconsistency.rb


(réduction)
module CustomCops
  class SpellInconsistency < RuboCop::Cop::Cop
    (réduction)
    NODE_TYPES = %I[str const sym lvasgn].freeze
    NODE_TYPES.each do |node_type|
      define_method "on_#{node_type}" do |node|
        SPELL_INCONSISTENCIES.each do |wrong_keyword, correct_keyword|
          add_offense(node, message: message(wrong_keyword, correct_keyword)) if node.source.include?(wrong_keyword)
        end
      end
    end
    (réduction)
  end
end

Lorsque vous exécutez rubocop --only CustomCops / SpellInconsistency target.rb ...

image.png

fanclub = 'a' a été détecté ... ʻa =' FanClub` a également été détecté ...

ruby-parse (2)

Examinons de plus près le code original et la sortie de ruby-parse.

(lvasgn :fanclub
  (str "a"))

L'affectation à une variable est «nom de variable = expression», n'est-ce pas? Je me demande si l'expression correspond à la chaîne de caractères «a». Si vous le regardez comme ceci, lvasgn est la valeur de gauche un signe de l'affectation du côté gauche. (Puisqu'il y a eu une analyse de syntaxe dans une classe universitaire il y a longtemps, je ne peux pas l'expliquer, mais je ne peux pas l'expliquer à un niveau que je peux comprendre ... je suis désolé.)

(Substitution côté gauche:fanclub
  (Chaîne"a")) 

La raison pour laquelle j'ai été coincé deux fois avec fanclub = 'a' était qu'il répondait à" fanclub "ʻof str et à" "fanclub" "de str in lvasgn`. Probablement de.

(lvasgn :a
  (str "fanclub"))

Il semble que vous ne devriez le prendre qu'immédiatement après lvasgn.

Si vous passez en revue Development Basic of RuboCop official documentation, vous trouverez les méthodes qui sont souvent utilisées dans l'argument node de ʻon_ ~. Était là. Vous devez utiliser children` et utiliser son premier enfant.

node.type # => :send
node.children # => [s(:send, s(:send, nil, :something), :empty?), :!]
node.source # => "!something.empty?"

custom_cop :cop: A part str, const, sym, j'ai fait ʻon_lvasgn` pour inspecter le premier enfant.

lib/custom_cops/spell_inconsistency.rb


(réduction)
module CustomCops
  class SpellInconsistency < RuboCop::Cop::Cop
(réduction)
    NODE_TYPES = %I[str const sym].freeze
    NODE_TYPES.each do |node_type|
      define_method "on_#{node_type}" do |node|
        SPELL_INCONSISTENCIES.each do |wrong_keyword, correct_keyword|
          add_offense(node, message: message(wrong_keyword, correct_keyword)) if node.source.include?(wrong_keyword)
        end
      end
    end

    def on_lvasgn(node)
      target = node.children.first
      SPELL_INCONSISTENCIES.each do |wrong_keyword, correct_keyword|
        add_offense(node, message: message(wrong_keyword, correct_keyword)) if target.match?(/#{wrong_keyword}/)
      end
    end
(réduction)
  end
end

Exécuter: lever_hands:

Lorsqu'il est exécuté en tant que rubocop --only CustomCops / SpellInconsistency target.rb ...

image.png

Ça a l'air bien: tada:

Test (RSpec): crayon:

Ce que nous avons fait jusqu'à présent, c'est ... vérifier les chaînes, les symboles, les constantes et les noms de variables. Compte tenu de la syntaxe de Ruby ... Il y a des noms de méthodes et des noms de classes juste pour trouver les parties à traiter. Vous remarquerez quelque chose après cela ... Ensuite, il était difficile d'exécuter le code pour l'inspection un par un, alors j'ai voulu écrire un test.

Paramètres: faux:

Les fichiers sous «spec / support» sont «obligatoires». Il semble que les personnes qui essaient d'entrer dans l'application plus tard sont souvent déjà configurées.

spec/spec_helper.rb


RSpec.configure do |config|
(réduction)
  Dir["#{__dir__}/support/**/*.rb"].sort.each { |f| require f }
end

Je charge le support RSpec pour rubocop et je charge custom_cop que j'ai ajouté.

spec/support/rubocop.rb


# frozen_string_literal: true

require 'rubocop'
require 'rubocop/rspec/support'
Dir["#{__dir__}/../../lib/**/*.rb"].sort.each { |f| require f }

RSpec.configure do |config|
  config.include(RuboCop::RSpec::ExpectOffense)
end

Test: crayon:

Le code écrit dans le code cible de vérification est transcrit dans le test, et la manière dont la sortie lorsque rubocop est exécutée est également décrite.

spec/lib/custom_cops/spell_inconstency_spec.rb


# frozen_string_literal: true

RSpec.describe CustomCops::SpellInconsistency do
  subject(:cop) { described_class.new }

  it 'Être capable de détecter les erreurs dans les chaînes de caractères' do
    expect_offense(<<-RUBY)
      fan_club = 'fanclub'
                 ^^^^^^^^^ Use 'fan_club' instead of 'fanclub'.
    RUBY
  end

  it 'Être capable de détecter les erreurs dans les symboles' do
    expect_offense(<<-RUBY)
      fan_club = :fanclub
                 ^^^^^^^^ Use 'fan_club' instead of 'fanclub'.
    RUBY
  end

  it 'Être capable de détecter des erreurs constantes' do
    expect_offense(<<-RUBY)
      fan_club = FANCLUB
                 ^^^^^^^ Use 'FAN_CLUB' instead of 'FANCLUB'.
    RUBY
  end

  it 'Être capable de détecter les erreurs dans les noms de variables' do
    expect_offense(<<-RUBY)
      fanclub = 'fan_club'
      ^^^^^^^^^^^^^^^^^^^^ Use 'fan_club' instead of 'fanclub'.
    RUBY
  end
end

Exécuter: lever_hands:

Lorsqu'il est exécuté avec rspec spec / lib / custom_cops / spell_inconstency_spec.rb ...

image.png

Ça a l'air bien: pouce levé:

Définition constante

J'ai défini les variables, mais je n'ai pas encore défini les constantes, alors je vais le faire.

Vérifiez le code cible: dart:

Code court pour les définitions de constante et de méthode.

target.rb


FANCLUB = 'a'

ruby-parse

Analysez avec ruby-parse.

image.png

La définition constante est «casgn», qui est un format différent de «lvasgn». C'était la seconde.

custom_cop :cop: J'ai fait une méthode de vérification pour casgn.

lib/custom_cops/spell_inconsistency.rb


(réduction)
module CustomCops
  class SpellInconsistency < RuboCop::Cop::Cop
(réduction)
    def on_casgn(node)
      target = node.children[1]
      SPELL_INCONSISTENCIES.each do |wrong_keyword, correct_keyword|
        add_offense(node, message: message(wrong_keyword, correct_keyword)) if target.match?(/#{wrong_keyword}/)
      end
    end
(réduction)
  end
end

Exécuter: lever_hands:

Lorsqu'il est exécuté en tant que rubocop --only CustomCops / SpellInconsistency target.rb ...

image.png

Ça a l'air bien: pouce levé:

Test: crayon:

Ajoutez-le au test pour que vous remarquiez s'il casse.

spec/lib/custom_cops/spell_inconstency_spec.rb


# frozen_string_literal: true

RSpec.describe CustomCops::SpellInconsistency do
  subject(:cop) { described_class.new }
(réduction)
  it 'Être capable de détecter les erreurs dans les noms constants' do
    expect_offense(<<-RUBY)
      FANCLUB = 'fan_club'
      ^^^^^^^^^^^^^^^^^^^^ Use 'FAN_CLUB' instead of 'FANCLUB'.
    RUBY
  end
end

Brosser: scintille:

À ce stade, j'ai en quelque sorte compris la procédure.

  1. Écrivez le morceau de code que vous voulez détecter (= écrivez le test)
  2. Analyser avec ruby-parse
  3. Mettre en œuvre

Répétez cette opération pour améliorer la précision de détection.

Définition de la méthode

Nous lui donnerons un nom lors de la définition de la méthode, alors jetons un œil. Comme il y a de nombreux éléments dans une ligne et qu'il existe de nombreux types d'éléments, nous les découperons en petits morceaux.

Nom de la méthode

Considérant un petit morceau de code, il ressemble à def set_fanclub; hoge; end.

En regardant ruby-parse ...

image.png

Le premier «enfant» de «def» est comme ça. Il est similaire au lvasgn utilisé lors de l'assignation à une variable. Comme pour str et const, utilisez define_method dans une boucle pour les regrouper.

lib/custom_cops/spell_inconsistency.rb


(réduction)
module CustomCops
  class SpellInconsistency < RuboCop::Cop::Cop
(réduction)
    NODE_TYPES_ONE = %I[str const sym].freeze
    NODE_TYPES_FIRST_CHILD = %I[lvasgn def].freeze

    NODE_TYPES_ONE.each do |node_type|
      define_method "on_#{node_type}" do |node|
        SPELL_INCONSISTENCIES.each do |wrong_keyword, correct_keyword|
          add_offense(node, message: message(wrong_keyword, correct_keyword)) if node.source.include?(wrong_keyword)
        end
      end
    end

    NODE_TYPES_FIRST_CHILD.each do |node_type|
      define_method "on_#{node_type}" do |node|
        target = node.children.first
        SPELL_INCONSISTENCIES.each do |wrong_keyword, correct_keyword|
          add_offense(node, message: message(wrong_keyword, correct_keyword)) if target.match?(/#{wrong_keyword}/)
        end
      end
    end
(réduction)
  end
end

argument

L'argument est ʻarg, qui a la même forme que lvasgn`, nous allons donc le résumer.

image.png

lib/custom_cops/spell_inconsistency.rb


(réduction)
module CustomCops
  class SpellInconsistency < RuboCop::Cop::Cop
(réduction)
    NODE_TYPES_FIRST_CHILD = %I[lvasgn def arg].freeze
(réduction)
  end
end

Valeur par défaut de l'argument

Vous pouvez donner des valeurs par défaut aux arguments, mais comme ils étaient les mêmes que «str», «sym» et «const», respectivement, n'ajoutez rien.

image.png

Argument de mot-clé

L'argument mot-clé est kwarg, qui a la même forme que lvasgn, nous allons donc le résumer.

image.png

lib/custom_cops/spell_inconsistency.rb


(réduction)
module CustomCops
  class SpellInconsistency < RuboCop::Cop::Cop
(réduction)
    NODE_TYPES_FIRST_CHILD = %I[lvasgn def arg kwarg].freeze
(réduction)
  end
end

Argument d'option de mot-clé

Lorsque la valeur par défaut est donnée à l'argument mot-clé, le nom de l'argument est kwoptarg, qui est de la même forme que lvasgn, nous allons donc le résumer.

image.png

lib/custom_cops/spell_inconsistency.rb


(réduction)
module CustomCops
  class SpellInconsistency < RuboCop::Cop::Cop
(réduction)
    NODE_TYPES_FIRST_CHILD = %I[lvasgn def arg kwarg kwoptarg].freeze
(réduction)
  end
end

hacher

Je me suis souvenu avoir fait l'argument mot-clé de la définition de méthode. Il était soutenu par le même symbole.

image.png

Définition de classe, définition de module

Le nom de la classe et le nom du module étaient «const».

image.png

image.png

Essayez-le

Je suis arrivé jusqu'ici ... J'ai l'impression qu'il y a encore beaucoup d'omissions. Cependant, depuis que j'écris un test, je sens que je peux le faire évoluer progressivement en fonction de ce que j'ai remarqué.

Mais ... c'était super facile à faire, mais c'était incroyablement difficile. Pas question, je vais mordre l'analyse syntaxique ... Cependant, je me suis lié d'amitié avec Rubocop. De plus, si vous souhaitez créer une règle dont vous ne vous souvenez pas avec votre code de travail, je vais l'essayer.

référence

Recommended Posts

Faisons un custom_cop qui souligne le tremblement du nom
Faire une marge à gauche du TextField
Faisons un robot! "Une simple démo de Java AWT Robot"
Faisons une application de calcul avec Java ~ Afficher la fenêtre de l'application
Jetons un coup d'œil à l'écran de Quant Analyzer!
Améliorons l'application
Rendre le vérificateur d'orthographe du code compatible avec le langage Ruby, qui signale les fautes d'orthographe avec VS Code.
Faisons une combinaison sans duplication | Premièrement, calculons le nombre total
Un mémo qui conteneurise l'application de chat simple de Node.js + socket.io
Une histoire qui a eu du mal avec l'introduction de Web Apple Pay
Comment identifier le chemin sur lequel il est facile de se tromper
Créons une application TODO en Java 5 Changer l'affichage de TODO
Écrivons un code facile à maintenir (Partie 2) Nom
Ceci et cela de JDK
Un mémorandum du problème FizzBuzz
Cas où les utilisateurs sont déconnectés lors de la modification d'une application (rails)
Un mémo sobrement accro à la demande de multipart / form-data
Une collection de phrases qui impressionne le "sentiment différent" de Java et de JavaScript
Exprimons le résultat de l'analyse du code d'octet Java dans un diagramme de classes
Comment savoir quelle version Java d'un fichier de classe a été compilée
Exemple de programme qui renvoie la valeur de hachage d'un fichier en Java