iceshrimp/CONTRIBUTING.md
2019-04-13 14:37:45 +09:00

5.5 KiB

Contribution guide

✌️ Thanks for your contributions ✌️

Issues

Feature suggestions and bug reports are filed in https://github.com/syuilo/misskey/issues . Before creating a new issue, please search existing issues to avoid duplication. If you find the existing issue, please add your reaction or comment to the issue.

Localization (l10n)

Please use Crowdin for localization.

Crowdin

Internationalization (i18n)

Misskey uses vue-i18n.

Documentation

  • Documents for contributors are located in /docs.
  • Documents for instance admins are located in /docs.
  • Documents for end users are located in src/docs.

Test

  • Test codes are located in /test.

Continuous integration

Misskey uses CircleCI for automated test. Configuration files are located in /.circleci.

Glossary

AP

Stands for ActivityPub.

MFM

Stands for Misskey Flavored Markdown.

Mk

Stands for Misskey.

SW

Stands for ServiceWorker.

Nyaize

Convert な(na) to にゃ(nya)

Denyaize

Revert Nyaize

Code style

Use semicolon

To avoid ASI Hazard

Don't use export default

Bad:

export default function(foo: string): string {

Good:

export function something(foo: string): string {

Directory structure

src ... Source code
	@types ... Type definitions
	prelude ... Independence utils for coding JavaScript without side effects
	misc ... Independence utils for Misskey without side effects
	service ... Common functions with side effects
	queue ... Job queues and Jobs
	server ... Web Server
	client ... Client
	mfm ... MFM

test ... Test code

Notes

placeholder

SQLをクエリビルダで組み立てる際、使用するプレースホルダは重複してはならない 例えば

query.andWhere(new Brackets(qb => {
	for (const type of ps.fileType) {
		qb.orWhere(`:type = ANY(note.attachedFileTypes)`, { type: type });
	}
}));

と書くと、ループ中でtypeというプレースホルダが複数回使われてしまいおかしくなる だから次のようにする必要がある

query.andWhere(new Brackets(qb => {
	for (const type of ps.fileType) {
		const i = ps.fileType.indexOf(type);
		qb.orWhere(`:type${i} = ANY(note.attachedFileTypes)`, { [`type${i}`]: type });
	}
}));

null in SQL

SQLを発行する際、パラメータがnullになる可能性のある場合はSQL文を出し分けなければならない 例えば

query.where('file.folderId = :folderId', { folderId: ps.folderId });

という処理で、ps.folderIdnullだと結果的にfile.folderId = nullのようなクエリが発行されてしまい、これは正しいSQLではないので期待した結果が得られない だから次のようにする必要がある

if (ps.folderId) {
	query.where('file.folderId = :folderId', { folderId: ps.folderId });
} else {
	query.where('file.folderId IS NULL');
}

[] in SQL

SQLを発行する際、INのパラメータが[](空の配列)になる可能性のある場合はSQL文を出し分けなければならない 例えば

const users = await Users.find({
	id: In(userIds)
});

という処理で、userIds[]だと結果的にuser.id IN ()のようなクエリが発行されてしまい、これは正しいSQLではないので期待した結果が得られない だから次のようにする必要がある

const users = userIds.length > 0 ? await Users.find({
	id: In(userIds)
}) : [];

配列のインデックス in SQL

SQLでは配列のインデックスは1始まり[a, b, c]aにアクセスしたいなら[0]ではなく[1]と書く

undefinedにご用心

MongoDBの時とは違い、findOneでレコードを取得する時に対象レコードが存在しない場合 undefined が返ってくるので注意。 MongoDBはnullで返してきてたので、その感覚でif (x === null)とか書くとバグる。代わりにif (x == null)と書いてください

簡素なundefinedチェック

データベースからレコードを取得するときに、プログラムの流れ的に(ほぼ)絶対undefinedにはならない場合でも、undefinedチェックしないとTypeScriptに怒られます。 でもいちいち複数行を費やして、発生するはずのないundefinedをチェックするのも面倒なので、ensureというユーティリティ関数を用意しています。 例えば、

const user = await Users.findOne(userId);
// この時点で user の型は User | undefined
if (user == null) {
	throw 'missing user';
}
// この時点で user の型は User

という処理をensureを使うと

const user = await Users.findOne(userId).then(ensure);
// この時点で user の型は User

という風に書けます。 もちろんensure内部でエラーを握りつぶすようなことはしておらず、万が一undefinedだった場合はPromiseがRejectされ後続の処理は実行されません。

const user = await Users.findOne(userId).then(ensure);
// 万が一 Users.findOne の結果が undefined だったら、ensure でエラーが発生するので
// この行に到達することは無い
// なので、.then(ensure) は
// if (user == null) {
//	throw 'missing user';
// }
// の糖衣構文のような扱いです