2024.07.24
STAFF BLOG
スタッフブログ
TECHNICAL
テクログ
![](https://core-tech.jp/wp-content/uploads/66c19942ab4ba346fdb64ccc04cde373-1.png)
1. はじめに
PHPCSをVSCodeで使用できるように拡張機能に組み込みます。PHPCSはコーディング規約違反を検出・修正するツールで、実行するためにはコマンドを叩く必要があります。しかしいちいちコマンドを叩くと面倒なため、ファイルを保存するとPHPCSが自動で実行される拡張機能を作成します。
このような拡張機能は探せばすぐ見つかりますが、自分で作成した方がちょっとした制御処理やカスタム機能を入れるときに便利です。
2. 環境
・WSL:2.2.4.0
・Ubuntu:20.04
・Node.js:v20.15.1
・VSCode:1.91.1
・PHP:8.3.8
・Composer:2.7.7
3. ひな形の作成
3.1 コマンド実行
下記コマンドを実行して、拡張機能のひな形を作成します。
npx --package yo --package generator-code -- yo code
コマンドを実行すると対話形式で質問されるので、下記のように回答します。
Q. What type of extension do you want to create?
A. New Extension (TypeScript)
Q. What’s the name of your extension?
A. phpcs-extension
Q. What’s the identifier of your extension?
A. 何も入力せずEnter
Q. What’s the description of your extension?
A. 何も入力せずEnter
Q. Initialize a git repository?
A. n ※git管理したい場合は「Y」
Q. Which bundler to use?
A. webpack
Q. Which package manager to use?
A. npm
3.2 動作確認
F5キーを押すことでデバッグモードに切り替えられます。デバッグモードに切り替えると、開発中の拡張機能が使用できるVSCodeのウィンドウが新しく作成されます。
「Ctrl + Shift + p」を押下して、「Hello World」と入力してEnterを押下します。ウィンドウの右下に「Hello World from phpcs-extension!」と書かれたダイアログが表示されたら問題なく動作しています。
3.3 activationEvents変更
package.jsonのactivationEventsを「onStartupFinished」に変更します。activationEventsは拡張機能を有効にするためのトリガーとなるイベントを登録する設定です。 onStartupFinishedはすべての拡張機能が有効になった後に、自身の拡張機能を有効にします。
{
...
"activationEvents": [
"onStartupFinished"
],
...
}
参考:https://code.visualstudio.com/api/references/activation-events#onStartupFinished
4. PHPCSインストール
下記コマンドを実行して、PHPCSのパッケージをインストールします。
composer require --dev "squizlabs/php_codesniffer=*"
動作確認のため、下記コードを記述したtest.phpという名前のファイルを作成します。
<?php
if(true){
echo 'hello world', PHP_EOL;
}
下記、phpcsコマンドを実行します。PSR12に準拠しない書き方が検出できていれば動作に問題はありません。
php vendor/bin/phpcs test.php --standard=PSR12
----------------------------------------------------------------------
FOUND 3 ERRORS AFFECTING 2 LINES
----------------------------------------------------------------------
3 | ERROR | [x] Expected 1 space after IF keyword; 0 found
3 | ERROR | [x] Expected 1 space after closing parenthesis; found 0
5 | ERROR | [x] Expected 1 newline at end of file; 0 found
----------------------------------------------------------------------
PHPCBF CAN FIX THE 3 MARKED SNIFF VIOLATIONS AUTOMATICALLY
----------------------------------------------------------------------
下記、phpcbfコマンドを実行します。phpcsコマンドで検出されたエラーのうち、「x」がついたエラーが自動で修正されていれば動作に問題はありません。
php vendor/bin/phpcbf test.php --standard=PSR12
<?php
if (true) {
echo 'hello world', PHP_EOL;
}
5. Formatter作成
5.1 実装
src/extension.tsを下記コードに書き換えます。
import * as vscode from 'vscode';
import * as child_process from 'child_process';
export function activate(context: vscode.ExtensionContext) {
// Formatter
const phpDocumentFormattingEditProviderDisposable = vscode.languages.registerDocumentFormattingEditProvider(
'php',
new DocumentFormattingEditProvider()
);
// Diagnostics
// ...
context.subscriptions.push(phpDocumentFormattingEditProviderDisposable);
}
class DocumentFormattingEditProvider implements vscode.DocumentFormattingEditProvider {
// 修正可能ステータス
private static readonly STATUS_FIXABLE = 1;
public provideDocumentFormattingEdits(
document: vscode.TextDocument,
options: vscode.FormattingOptions,
token: vscode.CancellationToken
): vscode.ProviderResult<vscode.TextEdit[]> {
// 整形範囲を取得
const firstLine = document.lineAt(0);
const lastLine = document.lineAt(document.lineCount - 1);
const range = new vscode.Range(firstLine.range.start, lastLine.range.end);
// phpcbfファイルのパスを取得
const phpcbfFilePath = vscode.workspace.getConfiguration('phpcs-extension').get('phpcbfFilePath');
if (typeof phpcbfFilePath !== 'string') {
return null;
}
// phpcbf実行
const res = child_process.spawnSync(phpcbfFilePath, ['-', '--standard=PSR12'], {
input: document.getText(),
});
// 整形後のファイルの中身を返す
return res.status === DocumentFormattingEditProvider.STATUS_FIXABLE
? [new vscode.TextEdit(range, res.stdout.toString())]
: null;
}
}
export function deactivate() {}
Formatterの実装にはDocumentFormattingEditProviderを使用します。DocumentFormattingEditProviderを継承したクラスを作成して、provideDocumentFormattingEditsメソッドを実装します。引数として整形しようとしているファイルの情報が渡されるので、整形後のファイルの中身を戻り値として返します。最後は、作成したクラスをregisterDocumentFormattingEditProviderで登録して完了です。
5.2 publisherとconfiguration追加
package.jsonにpublisherを追加します。ここでは「test-publisher」にします。
{
...
"description": "",
"publisher": "test-publisher",
"version": "0.0.1",
...
}
次にconfigurationを追加します。これは拡張機能でsettings.jsonに記述された値を取得したい場合に必要な設定です。コード上の処理「phpcbfファイルのパスを取得」の箇所で、phpcbfファイルの絶対パスを取得しているため、その設定を追加します。
{
...
"main": "./dist/extension.js",
"contributes": {
"configuration": {
"type": "object",
"title": "phpcs-extension",
"properties": {
"phpcs-extension.phpcbfFilePath": {
"type": "string",
"markdownDescription": "phpcbfファイルの絶対パス"
}
}
}
},
"scripts": {
...
}
元々あったコマンドの設定は不要なので削除しました。
5.3 debugディレクトリとsettings.json作成
デバッグ用のファイル置き場となるディレクトリを作成します。ワークスペース配下にdebugディレクトリを作成して、その中に.vscode/settings.jsonを作成します。
{
"[php]": {
"editor.defaultFormatter": "test-publisher.phpcs-extension",
"editor.formatOnSave": true,
},
"phpcs-extension.phpcbfFilePath": "phpcbfファイルの絶対パスに差し替える"
}
phpcs-extension.phpcbfFilePathの値は自分の環境に合わせて変更してください。phpcbfファイルはvendor/bin配下にあります。
5.4 動作確認
F5キーを押下してデバッグモードに切り替えます。新しく作成されたVSCodeのウィンドウで、先ほど作成したdebugディレクトリに移動します。
次に、テスト用のPHPファイルを作成します。ファイル名はtest.phpで、ファイルの中身は下記の通りです。
<?php
if(true){
echo 'Hello World', PHP_EOL;
}
ファイルを保存したときにtest.phpの中身が下記のようになれば、Formatterの機能が正常に動作したことになります。
<?php
if (true) {
echo 'Hello World', PHP_EOL;
}
6. Diagnostics作成
6.1 実装
src/extension.tsを下記コードのように追記・修正します。
// ...
export function activate(context: vscode.ExtensionContext) {
// Formatter
const phpDocumentFormattingEditProviderDisposable = vscode.languages.registerDocumentFormattingEditProvider(
'php',
new DocumentFormattingEditProvider()
);
// Diagnostics
const onDidSaveTextDocument = new OnDidSaveTextDocument();
const onDidSaveTextDocumentDisposable = vscode.workspace.onDidSaveTextDocument(
onDidSaveTextDocument.onDidSaveTextDocument
);
context.subscriptions.push(phpDocumentFormattingEditProviderDisposable, onDidSaveTextDocumentDisposable);
}
// ...
class OnDidSaveTextDocument {
private readonly diagnosticCollection: vscode.DiagnosticCollection;
public constructor() {
this.diagnosticCollection = vscode.languages.createDiagnosticCollection('php');
this.onDidSaveTextDocument = this.onDidSaveTextDocument.bind(this);
}
public onDidSaveTextDocument(document: vscode.TextDocument): void {
// Diagnosticsクリア
this.diagnosticCollection.clear();
// phpcsが無効化されていたら何もしない
const enablePhpcs = vscode.workspace.getConfiguration('phpcs-extension').get('enablePhpcs');
if (typeof enablePhpcs !== 'boolean' || !enablePhpcs) {
return;
}
// PHPファイルでなければ何もしない
if (document.languageId !== 'php') {
return;
}
// phpcsファイルのパスを取得
const phpcsFilePath = vscode.workspace.getConfiguration('phpcs-extension').get('phpcsFilePath');
if (typeof phpcsFilePath !== 'string') {
return;
}
// phpcs実行
const res = child_process.spawnSync(phpcsFilePath, [document.fileName, '--report=json']);
const json = JSON.parse(res.stdout.toString());
// Diagnostics作成
const diagnostics: vscode.Diagnostic[] = [];
for (const { message, source, type, line, column } of json['files'][document.fileName]['messages']) {
const range = new vscode.Range(line - 1, column - 1, line - 1, column - 1);
const msg = `${message}\n${source}`;
const severity = type === 'ERROR' ? vscode.DiagnosticSeverity.Error : vscode.DiagnosticSeverity.Warning;
diagnostics.push(new vscode.Diagnostic(range, msg, severity));
}
this.diagnosticCollection.set(document.uri, diagnostics);
}
}
// ...
6.2 settings.jsonに設定を追加
下記のようにsettings.jsonにConfigurationを追加します。追加する位置は、contributes > configuration > properties の配下です。
{
...
"phpcs-extension.phpcsFilePath": {
"type": "string",
"markdownDescription": "phpcsファイルの絶対パス"
},
"phpcs-extension.enablePhpcs": {
"type": "boolean",
"enum": [
true,
false
],
"markdownDescription": "コーディングルール違反検出の有効・無効"
}
...
}
6.3 Configuration追加
debug/.vscode/settings.jsonに下記の設定を追加します。
{
...
"phpcs-extension.phpcsFilePath": "phpcsファイルの絶対パスに差し替える",
"phpcs-extension.enablePhpcs": true
}
phpcbfと同様、 phpcs-extension.phpcsFilePathの値は自分の環境に合わせて変更してください。phpcsファイルもvendor/bin配下にあります。
6.3 動作確認
F5キーを押下してデバッグモードに切り替えます。新しく作成されたVSCodeのウィンドウは、先ほど作成したdebugディレクトリに移動しておきます。
先ほど作成したtest.phpを下記のコードに差し替えます。
<?php
class Hoge
{
public function snake_case(): void
{
return;
}
}
ファイルを保存したときに「function」の箇所に赤い波線が表示されていれば、Diagnoticsの機能が正常に動作したことになります。PSR準拠の場合、メソッド名はキャメルケースで宣言する必要がありますが、「snake_case」はスネークケースのためエラーとして扱われています。
![](https://core-tech.jp/wp-content/uploads/66c19942ab4ba346fdb64ccc04cde373-1.png)
7. 拡張機能をインストール可能なファイルとして出力
7.1 README.mdファイル変更
README.mdファイルに拡張機能の説明を記述します。このファイルの中身を変更しないと次の手順でエラーが出る可能性があります。
7.2 vsixファイルの作成
拡張機能をインストール可能なvsixファイルとして出力します。下記コマンドをして、phpcs-extension-0.0.1.vsixファイルを作成します。
npx vsce package
7.3 vsixファイルから拡張機能のインストール
phpcs-extension-0.0.1.vsixファイルを右クリックして表示される「拡張機能のVSIXのインストール」を選択することで、拡張機能をインストールできます。
8. おわりに
VSCode拡張機能を開発する際の参考になれば幸いです。