CDK プロジェクトに Biome を導入する
最終形
/*
AWS CDKの命名規則&コードスタイルに可能な限り準拠する
https://github.com/aws/aws-cdk/blob/main/docs/DESIGN_GUIDELINES.md#naming--style
その他はBiomeの推奨設定に従うが、必要に応じて変更する
https://biomejs.dev/analyzer/import-sorting/
https://biomejs.dev/formatter/#options
https://biomejs.dev/linter/rules/#recommended-rules
https://biomejs.dev/linter/rules/use-naming-convention/
*/
{
"$schema": "https://biomejs.dev/schemas/1.8.3/schema.json",
"files": {
"ignore": ["node_modules/*", "cdk.out/*"]
},
"formatter": {
"ignore": ["*.js", "tsconfig.json", "cdk.json", "*.snap"],
"indentStyle": "space",
"indentWidth": 2,
"lineWidth": 150
},
"javascript": {
"formatter": {
"quoteStyle": "single",
"semicolons": "always"
}
},
"linter": {
// "ignore": [],
"rules": {
"correctness": {
"noUndeclaredVariables": "error",
"noUnusedVariables": "error"
},
"style": {
"noNamespace": "error",
"useImportType": "off", // Biome推奨だが、あまり見かけない形のため無効化
"useNamingConvention": {
"level": "error",
"options": {
"strictCase": false,
"conventions": [
{ "selector": { "kind": "class" }, "formats": ["PascalCase"] },
{ "selector": { "kind": "classMethod" }, "formats": ["camelCase"] },
{ "selector": { "kind": "typeMethod" }, "formats": ["camelCase"] },
{ "selector": { "kind": "objectLiteralMethod" }, "formats": ["camelCase"] },
{ "selector": { "kind": "function" }, "formats": ["camelCase"] },
{ "selector": { "kind": "interface" }, "formats": ["PascalCase"] },
{ "selector": { "kind": "enum" }, "formats": ["PascalCase"] },
{ "selector": { "kind": "enumMember" }, "formats": ["CONSTANT_CASE"] },
{ "selector": { "kind": "objectLiteralProperty" }, "formats": ["camelCase", "PascalCase", "CONSTANT_CASE"] },
{ "selector": { "kind": "importAlias" }, "formats": ["camelCase", "PascalCase", "snake_case", "CONSTANT_CASE"] }
]
}
}
},
"suspicious": {
"noEmptyBlockStatements": "error",
"noSkippedTests": "warn"
}
}
},
// テストコードに含まれるJest関数が no undeclared variables と判定されるため、テストファイルのみ設定を上書き
"overrides": [
{
"include": ["*.test.ts", "*.test.tsx", "*.spec.ts", "*.spec.tsx", "**/__tests__/**"],
"javascript": {
"globals": ["afterAll", "afterEach", "beforeAll", "beforeEach", "describe", "expect", "it", "jest", "test"]
}
}
]
}
環境
$ cat /etc/system-release
Amazon Linux release 2023.5.20240708 (Amazon Linux)
$ uname -a
Linux example.ap-northeast-1.compute.internal 6.1.79-99.164.amzn2023.x86_64 #1 SMP PREEMPT_DYNAMIC Tue Feb 27 18:02:23 UTC 2024 x86_64 x86_64 x86_64 GNU/Linux
$ node -v
v18.17.0
$ npm --version
9.6.7
$ cdk --version
2.155.0 (build 34dcc5a)
cdk init app --language typescript
実行済みディレクトリ
# Installation
$ npm install --save-dev --save-exact @biomejs/biome
# Configuration
# jsonではなくコメントや末尾カンマが付けられるjsoncを扱いたいのでjsoncオプションを追加
$ npx @biomejs/biome init --jsonc
Welcome to Biome! Let's get you started...
Files created
- biome.jsonc
Your project configuration. See https://biomejs.dev/reference/configuration
Next Steps
1. Setup an editor extension
Get live errors as you type and format when you save.
Learn more at https://biomejs.dev/guides/integrate-in-editor/
2. Try a command
biome check checks formatting, import sorting, and lint rules.
biome --help displays the available commands.
3. Migrate from ESLint and Prettier
biome migrate eslint migrates your ESLint configuration to Biome.
biome migrate prettier migrates your Prettier configuration to Biome.
4. Read the documentation
Find guides and documentation at https://biomejs.dev/guides/getting-started/
5. Get involved with the community
Ask questions and contribute on GitHub: https://github.com/biomejs/biome
Seek for help on Discord: https://discord.gg/BypW39g6Yc
実行後
{
"$schema": "https://biomejs.dev/schemas/1.8.3/schema.json",
"organizeImports": {
"enabled": true
},
"linter": {
"enabled": true,
"rules": {
"recommended": true
}
}
}
{
"name": "example",
"version": "0.1.0",
"bin": {
"example": "bin/example.js"
},
"scripts": {
"build": "tsc",
"watch": "tsc -w",
"test": "jest",
"cdk": "cdk"
},
"devDependencies": {
+ "@biomejs/biome": "1.8.3",
"@types/jest": "^29.5.12",
"@types/node": "20.14.9",
"aws-cdk": "2.155.0",
"jest": "^29.7.0",
"ts-jest": "^29.1.5",
"ts-node": "^10.9.2",
"typescript": "~5.5.3"
},
"dependencies": {
"aws-cdk-lib": "2.155.0",
"constructs": "^10.0.0",
"source-map-support": "^0.5.21"
}
}
biome check
要求されたファイルに対してフォーマッタ、リンター、インポートのソートを実行します。--write — 安全な修正、フォーマット、インポートのソートを書き込む。
--apply — --writeのエイリアスで、安全な修正、書式設定、インポートの並べ替えを行う(非推奨です。--writeを使用してください)
biome ci
CI 環境で使用するコマンド。要求されたファイルに対してフォーマッタ、リンター、インポートのソートを実行します。
ファイルは変更されません。コマンドは読み取り専用操作です。
Biomeコマンドをnpm scriptsに追加
{
"name": "example",
"version": "0.1.0",
"bin": {
"example": "bin/example.js"
},
"scripts": {
"build": "tsc",
"watch": "tsc -w",
"test": "jest",
"cdk": "cdk",
+ "format": "npx @biomejs/biome format --write .",
+ "lint": "npx @biomejs/biome lint --write .",
+ "check": "npx @biomejs/biome check --write .",
+ "check:ci": "npx @biomejs/biome ci ."
},
"devDependencies": {
"@biomejs/biome": "1.8.3",
"@types/jest": "^29.5.12",
"@types/node": "20.14.9",
"aws-cdk": "2.155.0",
"jest": "^29.7.0",
"ts-jest": "^29.1.5",
"ts-node": "^10.9.2",
"typescript": "~5.5.3"
},
"dependencies": {
"aws-cdk-lib": "2.155.0",
"constructs": "^10.0.0",
"source-map-support": "^0.5.21"
}
}
linterやformatterはCDK公式のコーディングスタイルに合わせたい
Naming & Style
Naming Conventions
- Class names: PascalCase
- Properties: camelCase
- Methods (static and non-static): camelCase
- Interfaces (“behavioral interface”): IMyInterface
- Structs (“data interfaces”): MyDataStruct
- Enums: PascalCase, Members: SNAKE_UPPER
Coding Style
- Indentation: 2 spaces
- Line length: 150
- String literals: use single-quotes (
'
) or backticks (```)- Semicolons: Semicolons: at the end of each code statement and declaration (incl. properties and imports).
- Comments: start with lower-case, end with a period.
-
Properties: camelCase
CDKの命名規則から外れるが、スナップショットテストやるときにCfnのプロパティ名(VisibilityTimeout)とかがPascalCaseだったりするので例外にしたい -
Comments: start with lower-case, end with a period.
コメントは日本語なので、これは無くても良さそう
良さそうなテンプレートがあった
Biome は言語に依存しないオプションと言語固有のオプションを分離します。
Formatterデフォルト設定
{
"formatter": {
"enabled": true,
"formatWithErrors": false,
"ignore": [],
"attributePosition": "auto",
"indentStyle": "tab",
"indentWidth": 2,
"lineWidth": 80,
"lineEnding": "lf"
},
"javascript": {
"formatter": {
"arrowParentheses":"always",
"bracketSameLine": false,
"bracketSpacing": true,
"jsxQuoteStyle": "double",
"quoteProperties": "asNeeded",
"semicolons": "always",
"trailingCommas": "all"
}
},
"json": {
"formatter": {
"trailingCommas": "none"
}
}
}
デフォルトでは、Biome リンターは推奨ルール↓のみを実行します。
行単位の除外設定
開発者がコードの特定の行の lint ルールを無視したい場合があります。これは、lint 診断を発行する行の上に抑制コメントを追加することで実現できます。
// biome-ignore lint: <explanation>
// biome-ignore lint/suspicious/noDebugger: <explanation>
biome-ignore
抑制コメントの開始です。lint
リンターを抑制します。/suspicious/noDebugger
:オプション、抑制するルールのグループと名前。<explanation>
ルールが無効になっている理由の説明
Biome では、自然な順序付けを使用してインポート ステートメントを並べ替えることができます。
この機能はデフォルトで有効になっていますが、設定によって無効にすることができます。
命名規則はここで定義
SNAKE_UPPER は CONSTANT_CASE と指定する
良さげなテンプレートを参考に、設定
/*
AWS CDKの命名規則&コードスタイルに可能な限り準拠する
https://github.com/aws/aws-cdk/blob/main/docs/DESIGN_GUIDELINES.md#naming--style
その他はBiomeの推奨設定に従うが、必要に応じて変更する
https://biomejs.dev/analyzer/import-sorting/
https://biomejs.dev/formatter/#options
https://biomejs.dev/linter/rules/#recommended-rules
https://biomejs.dev/linter/rules/use-naming-convention/
*/
{
"$schema": "https://biomejs.dev/schemas/1.8.3/schema.json",
"files": {
"ignore": ["node_modules/*", "cdk.out/*"]
},
"formatter": {
"ignore": ["*.js", "cdk.json", "package.json", "package-lock.json", "tsconfig.json", "*.snap"],
"indentStyle": "space",
"indentWidth": 2,
"lineWidth": 150
},
"javascript": {
"formatter": {
"quoteStyle": "single",
"semicolons": "always"
}
},
"linter": {
// "ignore": [],
"rules": {
"correctness": {
"noUndeclaredVariables": "error",
"noUnusedVariables": "error"
},
"style": {
"noNamespace": "error",
"useImportType": "off", // Biome推奨だが、あまり見かけない形のため無効化
"useNamingConvention": {
"level": "error",
"options": {
"strictCase": false,
"conventions": [
{ "selector": { "kind": "class" }, "formats": ["PascalCase"] },
{ "selector": { "kind": "classMethod" }, "formats": ["camelCase"] },
{ "selector": { "kind": "typeMethod" }, "formats": ["camelCase"] },
{ "selector": { "kind": "objectLiteralMethod" }, "formats": ["camelCase"] },
{ "selector": { "kind": "function" }, "formats": ["camelCase"] },
{ "selector": { "kind": "interface" }, "formats": ["PascalCase"] },
{ "selector": { "kind": "enum" }, "formats": ["PascalCase"] },
{ "selector": { "kind": "enumMember" }, "formats": ["CONSTANT_CASE"] },
{ "selector": { "kind": "objectLiteralProperty" }, "formats": ["camelCase", "PascalCase", "CONSTANT_CASE"] },
{ "selector": { "kind": "importAlias" }, "formats": ["camelCase", "PascalCase", "snake_case", "CONSTANT_CASE"] }
]
}
}
},
"suspicious": {
"noEmptyBlockStatements": "error",
"noSkippedTests": "warn"
}
}
}
}
コメントに挟まれている空行が削除されるのが割と気になるが、設定を変更できるFormatterオプションは無し
Biomeを使うなら割り切るしかなさそう
#!/usr/bin/env node
import 'source-map-support/register';
import * as cdk from 'aws-cdk-lib';
import { ExampleStack } from '../lib/example-stack';
const app = new cdk.App();
new ExampleStack(app, 'ExampleStack', {
/* If you don't specify 'env', this stack will be environment-agnostic.
* Account/Region-dependent features and context lookups will not work,
* but a single synthesized template can be deployed anywhere. */
-
/* Uncomment the next line to specialize this stack for the AWS Account
* and Region that are implied by the current CLI configuration. */
// env: { account: process.env.CDK_DEFAULT_ACCOUNT, region: process.env.CDK_DEFAULT_REGION },
-
/* Uncomment the next line if you know exactly what Account and Region you
* want to deploy the stack to. */
// env: { account: '123456789012', region: 'us-east-1' },
-
/* For more information, see https://docs.aws.amazon.com/cdk/latest/guide/environments.html */
});
// import * as cdk from 'aws-cdk-lib';
// import { Template } from 'aws-cdk-lib/assertions';
// import * as Example from '../lib/example-stack';
// example test. To run these tests, uncomment this file along with the
// example resource in lib/example-stack.ts
test('SQS Queue Created', () => {
// const app = new cdk.App();
// // WHEN
// const stack = new Example.ExampleStack(app, 'MyTestStack');
// // THEN
// const template = Template.fromStack(stack);
-
// template.hasResourceProperties('AWS::SQS::Queue', {
// VisibilityTimeout: 300
// });
});
```
BLEAで試してみる
安全な修正では、以下3つが修正されていた
- Max Line150の改行
- importの並べ替え
- 同名のas削除
--unsafeを付けると、以下が修正されていた
- グレイヴ・アクセント(``)をシングルクォートに('')
- lambdaのexportするメソッドで使ってない引数のeventやcontextを_eventや_contextに、var変数をconstに
- 変数含む文字列結合を+から`${var}`で展開する形へ
テストコードに含まれるJestの関数が、軒並み未宣言としてエラーに
./usecases/blea-guest-ecs-app-sample/test/blea-guest-ecs-app-sample-pipeline.test.ts:22:3 lint/correctness/noUndeclaredVariables ━━━━━━━━━━
✖ The expect variable is undeclared.
20 │ });
21 │
> 22 │ expect(Template.fromStack(pipeline)).toMatchSnapshot();
│ ^^^^^^
23 │ });
24 │
ℹ By default, Biome recognizes browser and Node.js globals.
You can ignore more globals using the javascript.globals configuration.
./usecases/blea-guest-ecs-app-sample/test/blea-guest-ecs-app-sample-pipeline.test.ts:7:1 lint/correctness/noUndeclaredVariables ━━━━━━━━━━
✖ The test variable is undeclared.
5 │ import { devParameter, devPipelineParameter } from '../parameter';
6 │
> 7 │ test('Snapshot test for BLEA ECS App Stacks', () => {
│ ^^^^
8 │ const app = new App();
9 │ const pipeline = new BLEAEcsAppPipelineStack(app, 'Dev-BLEAEcsAppPipeline', {
ℹ By default, Biome recognizes browser and Node.js globals.
You can ignore more globals using the javascript.globals configuration.
overridesでテストに使うファイルでのみ、無視するglobalsとして指定を追加
"overrides": [
{
"include": ["*.test.ts", "*.test.tsx", "*.spec.ts", "*.spec.tsx", "**/__tests__/**"],
"javascript": {
"globals": ["afterAll", "afterEach", "beforeAll", "beforeEach", "describe", "expect", "it", "jest", "test"]
}
}
]
命名規則でもエラーが出ている
これはBLEAのここだけでのケースなので無視してもよさそう
./usecases/blea-guest-ec2-app-sample/lib/construct/investigation-instance.ts:11:19 lint/style/useNamingConvention ━━━━━━━━━━
✖ This class property name should be in camelCase.
10 │ export class InvestigationInstance extends Construct {
> 11 │ public readonly InvestigationInstanceSecurityGroup: ec2.ISecurityGroup;
│ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
12 │
13 │ constructor(scope: Construct, id: string, props: InvestigationInstanceProps) {
DynamoDBに入れるアイテムのプロパティ名は問題無いので、objectLiteralProperty
にsnake_case
を追加しても良いかも
./usecases/blea-guest-serverless-api-sample/lambda/nodejs/putItem.js:15:7 lint/style/useNamingConvention ━━━━━━━━━━
✖ This object property name should be in camelCase or PascalCase or CONSTANT_CASE.
13 │ title: { S: request.title },
14 │ content: { S: request.content },
> 15 │ created_at: { S: datetime },
│ ^^^^^^^^^^
16 │ },
17 │ };
./usecases/blea-gov-base-ct/lib/stack/blea-gov-base-ct-via-cdk-pipelines-stack.ts:33:5 lint/complexity/noForEach ━━━━━━━━━━
✖ Prefer for...of instead of forEach.
31 │ });
32 │
> 33 │ props.targetParameters.forEach((params) => {
│ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
> 34 │ pipeline.addStage(new BLEAGovBaseCtStage(this, 'Dev', params));
> 35 │ });
│ ^^
36 │ }
37 │ }
./usecases/blea-guest-ecs-app-sample/lib/stack/blea-guest-ecs-app-sample-via-cdk-pipelines-stack.ts:48:5 lint/complexity/noForEach ━━━━━━━━━━
✖ Prefer for...of instead of forEach.
46 │ });
47 │
> 48 │ props.targetParameters.forEach((params) => {
│ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
> 49 │ pipeline.addStage(new BLEAEcsAppStage(this, 'Dev', params));
> 50 │ });
│ ^^
51 │ }
52 │ }
ℹ forEach may lead to performance issues when working with large arrays. When combined with functions like filter or map, this causes multiple iterations over the same type.
Stack定義周りでステージのAppParameterとかでよく使う印象なので一旦保留
CDKにも試してみる
相当量のエラーが出ていた
Skipped 239924 suggested fixes.
If you wish to apply the suggested (unsafe) fixes, use the command biome check --fix --unsafe
The number of diagnostics exceeds the number allowed by Biome.
Diagnostics not shown: 26969.
Checked 14045 files in 47s. Fixed 12268 files.
Found 14882 errors.
Found 4 warnings.
snapshot含む、テスト関係のファイルも対象に含まれてしまっていたが、cdk公式のようなディレクトリ構成にはしないので無視
その他、150文字以内なのに改行されたり、超えていて改行されているのに1行に戻されたりと前後の構文を解釈した上で差分が出ている箇所も多かった
(デフォルトで)Biomeに無視されますと記載があるが、tsconfig.json はignoreしないと修正対象になってしまう
VSCode拡張機能を設定
一旦全部設定
{
"recommendations": ["biomejs.biome"]
}
{
"editor.defaultFormatter": "biomejs.biome",
"editor.formatOnSave": true,
"editor.codeActionsOnSave": {
"quickfix.biome": "explicit",
"source.organizeImports.biome": "explicit"
}
}
VSCodeで開いているウィンドウのホームディレクトリに作成した.vscode
フォルダに入れれば、Formatterとimportのソートがちゃんと保存時に反映された
CDKのベストプラクティスはESLint と Prettier
BiomeのCI設定例
name: Code quality
on:
push:
pull_request:
jobs:
quality:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Biome
uses: biomejs/setup-biome@v2
with:
version: latest
- name: Run Biome
run: biome ci .