見出し画像

[Python]セミコロンチェッカー作ってみた

今回は、クリップボードに保存された文字列の末尾にセミコロンがあるかどうかをチェックするツールをPythonで作ってみました。

(2020/7/26:switch~case文でのコロン「:」をOKする処理追加)
(2020/8/17:実行中の表示追加)
(2020/8/22:オブジェクトリテラルの}の後をエラーにする処理追加)

セミコロンを行末につけるプログラムコードのチェックに使えます。このチェッカーはPythonで書いていますが、Pythonのコードはセミコロン不要なので使う意味はないです。JavaScriptやC言語用になります。
perlもセミコロンを使うようですが、#のコメントに対応していないので、現状は使えません。

そもそもの始まりは

2019年10月頃から、ノンプロ研でプログラミング等の勉強をしています。

ノンプロ研のPython初心者講座を受講して以来、ずっとPythonメインで勉強していましたが、この度ついにGAS(Google Apps Script)の初心者講座に参加し、GASの勉強を始めました。

GASはJavaScriptという言語ベースで書かれています。Pythonとの違いの一つとして実行文の行末にセミコロン「;」が必要ですが、Pythonのクセでコードを書いているとセミコロンは忘れがちです。厄介なのは、スクリプトエディタ上のチェッカーもないのに加え、セミコロンを忘れても必ずエラーが出るわけではなく、実行できてしまうこともあるということです。

ノンプロ研のGAS講座では宿題でコードをSlackで提出するのですが、うっかりセミコロンを忘れて提出すると「セミコロンポリス」がどこからともなく現れて、下のようなスタンプを回答に押されてしまいます。(笑)

画像1

講座は毎週で計6回あり、宿題も1回でいくつか出されるので、いつか必ずセミコロンを忘れてしまう時が来るでしょう。そこで、セミコロンポリス対策として、チェッカーを作ろうというのを講座第1回の後の懇親会で話していて思いつきました。

調べればWeb上で動くチェッカーとかはあるのでしょうが、せっかく思いついたので、Pythonの復習を兼ねて作ってみました。

セミコロンチェッカーの概要

GAS講座では、ブラウザ上で動作するスクリプトエディタでコードを書きます。動作チェックをした後、宿題提出するにはスクリプトエディタ上でクリップボードにコピーし、Slackのコードスニペットに貼り付けて提出します。

そこで、クリップボードに保存されているコードの中身を読み取り、行末にセミコロンがない行をエラーとして出力することにしました。

私はGASはまだ超初心者で何もできないので、Pythonで書くことにしました。Pythonはクリップボードの情報を読み取るモジュールがあるので、そこそこ簡単にできそうです。

あと、GASを書いている時にPython環境(プロンプトやVS Code)を立ち上げておくのもなんか嫌なので、tkinterを使ってGUI上のみで実行できるようにしました。(これ地味に便利です。あとはPythonコードをダブルクリックで実行できる設定をしておけば、なお良し)

セミコロンチェッカーのPythonコード

ソースコードは以下です。最初は10行くらいだったのですが、コメントアウト行の処理等を追加していったら結構な長さのコードになってしまいました。。

import time
import re
import tkinter as tk
from tkinter import ttk
import pyperclip

# 空行はOK
# 波括弧{、カンマ、セミコロン、コロンで終わる行はOK
# */で終わる行(コメント)はOK
# オブジェクトリテラル以外の波括弧}で終わる行はOK (2020.8.21追加)
# 複数行に跨るテンプレート文字列は現状未対応のためエラーとなる。

def check_semicolon():

   # 実行中表示:tkinterのラベル更新(labelはglobal変数)
   label['text'] = '実行中'
   label.update()
   time.sleep(1)

   # クリップボードを1行毎のリストに格納
   list_clipboard = pyperclip.paste().splitlines()

   # クリップボード内がfunctionで始まり、}で終わっている場合にのみチェック実施
   # 最初のlist_clipboardは、クリップボードが文字列でない場合にFalseが入り、対象外とするため
   if list_clipboard and list_clipboard[0].startswith('function ') and list_clipboard[-1] == '}':

       flag_comment = False
       stack_isObject_brace = []
       result = ''
       line_number = 0
       for line in list_clipboard:
           line_number += 1
           
           # 文字列リテラル内の文字列を削除(/*,*/があるとコメントと誤判定するため)
           # //から後の文字は削除(コメント)
           # 空白削除(空白のみの行やセミコロンの後に空白がある場合を想定)
           line = re.sub('(".*")|(\'.*\')|(`.*`)|//.*| ', '', line)

           # 複数行コメント対応 -> /*から*/の間はチェックしない
           if '/*' in line:
               flag_comment = True
           if '*/' in line:
               flag_comment = False
           if flag_comment:
               continue # コメント行の場合はノーチェックで次の行へスキップ

           if '{' in line:
               #オブジェクトの }が来たらTrueをスタック
               #オブジェクト以外の {が来たらFalseをスタック
               stack_isObject_brace.append(re.search('= *{', line))
           if '}' in line:
               isObject = stack_isObject_brace.pop() #最後の {をポップ
               if not isObject and line.endswith('}'):
                   continue # オブジェクト以外で}で終わる行はノーチェックで次の行へスキップ

           # {か}が1行に2個以上あると};の判定ができないため、メッセージを出す
           if line.count('{') >= 2 or line.count('}') >= 2:
               result += f'WARNING: 波括弧が1行に複数あるよ (line:{line_number}) {line}\n'

           # エラー行でない場合はスキップ
           if line == '' or line.endswith(('{', ',', ';', '*/', ":")):
               continue
           
           # エラー行の情報をresultに追加
           result += f'ERROR: セミコロンがないっすよ! (line:{line_number}) {line}\n'
           
       if not result:
           # エラーなしの場合はOKを表示
           result = 'OK!'
       
   else:
       # コードじゃないものがクリップボードに入っている場合
       result = 'コードの\n\nfunction ~\n    :\n }\n\nまでをクリップボードにコピーして下さい'

   # tkinterのラベル更新(labelはglobal変数)
   label['text'] = result
   label.update()

# GUI設定
root = tk.Tk()
root.title('セミコロンチェッカー')
button = ttk.Button(text='チェック実行', command=check_semicolon)
button.pack()
label = ttk.Label(text='チェック結果が表示されます', font=('メイリオ', 12))
label.pack()
root.mainloop()

クリップボードの内容を読み出すのにpyperclipというモジュールを使っていますが、pyperclipはサードパーティ製ですので、pipを使って事前にインストールをしておく必要があります。

セミコロンの判定方法は以下の通りです。私はGAS超初心者で文法自体をまだ理解していないので、本来はセミコロンが不要な行もエラーになってしまうかもしれません。問題あれば、その都度updateしていこうかと思います。
(2020/7/26:switch~case文でのコロン「:」をOKする処理追加)
(2020/8/22:オブジェクトリテラルの}の後をエラーにする処理追加)

チェック仕様は下記の行をOKとし、それ以外をエラーとしています。
・セミコロンで終わっている(セミコロン後の空白はOK)
・カンマで終わっている(オブジェクト等を複数行で記述する場合を想定)
・波括弧「{」または「}」で終わっている
 ただしオブジェクトリテラルの}の後はセミコロンがないとエラー
・スペースのみの行
・改行のみの行
・// 以降(コメント行)
・/* ~ */ の内部(複数行コメント内もOK)
・コロンで終わっている(switch~case文を想定)

セミコロンチェッカーの使い方

Pythonコードを実行すると、以下のようなGUIが出てきます。

画像2

エディタ上でチェックしたいコードを選択し、Ctrl-Cでクリップボードにコピーします。コードは必ず「function」から「}」までの範囲を選択して下さい。

function myFunction1_01() {
 console.log(123);
 console.log('Hello GAS!')
}

クリップボードにコピーしたら、GUIの「チェック実行」ボタンをポチっと押すと、チェッカーが動作します。1秒程度「実行中」の表示となり、以下のような結果が出ます。
(2020/8/17:続けて実行する際に、エラーがないとOK表示のまま何も変化がないので、チェックがされているのかどうかわからないため、実行中の表示を追加)

画像5

3行目の最後にセミコロンがないということが分かります。そこでコードを以下のように修正してみました。

function myFunction1_01() {
 console.log(123);
 console.log('Hello GAS!');
}

これを再度クリップボードにコピーし、チェック実行を押してみます。

画像4

OKでました~。これでセミコロンポリスが現れることもないでしょう。(笑)

ちなみに先程、『コードは必ず「function」から「}」までの範囲を選択して下さい』と書きましたが、それ以外の範囲やテキスト以外のデータがクリップボードに格納されている場合は、以下のようなメッセージが出ます。

画像5

「function」で始まって「}」で終わるかのみをチェックしているので、複数のfuctionを同時にコピーするのはOKです。コピーした範囲全てをチェックします。

既知の問題

テンプレート文字列(「`」で囲まれる部分)を複数行に跨って記述する場合は行末にセミコロンがない行としてエラー出力されてしまいます。もし今後コードを書いていく上で多用する場合は対応したいと思います。

まとめ

今回このツールを作って、前にコピペ+改造で作ったGASのコードで試してみましたが、実際セミコロンがない箇所がいくつか見つかりました。いずれもちゃんと動作はしています。(汗)
よって、チェッカーが有効だということが明らかになりました。今後活用していきたいと思います。

それにしても、GASの学習を始めてすぐにPythonコードを書くことになるとは想像していませんでした。(笑)

コードを書く時はいつもそうですが、今回のコードも何も見ないで書けたわけではなく、Webや本で調べながら書きました。Pythonを今まで学んできた結果、実現できることがある程度把握できているから調べることができたのだと思います。

GASも継続して勉強し、Pythonと同様に何が実現できるかを把握できるようになって、「あれ、これGASで出来るよね?ちょっとやってみよう」くらいにはなりたいなと思います。がんばるぞー

最後まで読んで頂き、ありがとうございました。