メインコンテンツまでスキップ

noImplicitReturns

noImplicitReturnsは、直訳すると「暗黙的なreturnの禁止」という意味で、関数のすべての分岐できちんと値を返しているかを検査するオプションです。

  • デフォルト: false
  • 追加されたバージョン: 1.8

解説

JavaScriptでは、関数が明示的にreturnをしなくても、暗黙的にundefinedを返す仕様があります。

js
function doSomething() {
// return文がない
}
 
const value = doSomething();
console.log(value);
undefined
js
function doSomething() {
// return文がない
}
 
const value = doSomething();
console.log(value);
undefined

つまり、上の関数は次のようなreturn undefinedを書いた関数と同じ意味になります。

js
function doSomething() {
return undefined;
}
js
function doSomething() {
return undefined;
}

この仕様にはひとつ問題があります。「本当にundefinedを返したくてreturn文を省略したのか」「return文を書き忘れて、意図せずundefinedを返しているのか」がはっきりしないという問題です。もし単なる書き忘れだった場合は、バグにつながります。

js
function getValue(map, key) {
if (key in map) {
return map[key];
}
// この経路では、undefinedを返すことを意図している?
// それとも本当はnullを返したかった?
// もしくは例外を投げるべきだった?
}
js
function getValue(map, key) {
if (key in map) {
return map[key];
}
// この経路では、undefinedを返すことを意図している?
// それとも本当はnullを返したかった?
// もしくは例外を投げるべきだった?
}

そのため、JavaScriptのベストプラクティスとしては、本当にundefinedを返すつもりなら、明示的にreturn文を書くことが推奨されています。

js
function getValue(map, key) {
if (key in map) {
return map[key];
}
return undefined;
}
js
function getValue(map, key) {
if (key in map) {
return map[key];
}
return undefined;
}

関数が複雑になるほど、どこかの分岐でreturnの書き忘れ事故が発生しやすくなります。次の例はさほど複雑でないものの、「境界値の処理を忘れる」という典型的なミスを含んだ例です。

ts
function negaposi(num: number) {
if (num > 0) {
return "positive";
}
if (num < 0) {
return "negative";
}
// num === 0 の経路で return を書き忘れている
// → この関数は暗黙的に undefined を返す
}
 
console.log(negaposi(0));
ts
function negaposi(num: number) {
if (num > 0) {
return "positive";
}
if (num < 0) {
return "negative";
}
// num === 0 の経路で return を書き忘れている
// → この関数は暗黙的に undefined を返す
}
 
console.log(negaposi(0));

noImplicitReturnsを有効にすると、returnを書き忘れた関数を検出できるようになります。たとえば、次のようなコードはエラーになります。

ts
function negaposi(num: number) {
Not all code paths return a value.7030Not all code paths return a value.
if (num > 0) {
return "positive";
}
if (num < 0) {
return "negative";
}
// return忘れ
}
ts
function negaposi(num: number) {
Not all code paths return a value.7030Not all code paths return a value.
if (num > 0) {
return "positive";
}
if (num < 0) {
return "negative";
}
// return忘れ
}

すべての経路で値を返すように修正すると、コンパイルが通ります。

ts
function negaposi(num: number) {
if (num > 0) {
return "positive";
}
if (num < 0) {
return "negative";
}
return "zero"; // return漏れを修正
}
ts
function negaposi(num: number) {
if (num > 0) {
return "positive";
}
if (num < 0) {
return "negative";
}
return "zero"; // return漏れを修正
}

returnなしが許容されるケース

noImplicitReturnsを有効にした場合でも、利便性のためにreturnなしが許容されるケースがあります。

まず、throwで終わる分岐は許容されます。

ts
function negaposi(num: number) {
if (num > 0) {
return "positive";
}
if (num < 0) {
return "negative";
}
throw new Error("this is 0"); // returnなしでもエラーにならない
}
ts
function negaposi(num: number) {
if (num > 0) {
return "positive";
}
if (num < 0) {
return "negative";
}
throw new Error("this is 0"); // returnなしでもエラーにならない
}

次に、戻り値の型注釈がvoidの場合もreturnなしが許容されます。

ts
function log(message?: string): void {
// ^^^^型注釈
if (!message) {
return;
}
console.log(message);
// returnなしでもエラーにならない
}
ts
function log(message?: string): void {
// ^^^^型注釈
if (!message) {
return;
}
console.log(message);
// returnなしでもエラーにならない
}

戻り値の型注釈がstring | voidのようなユニオン型の場合もreturnなしの経路が許容されます。

ts
function negaposi(num: number): string | void {
// ^^^^^^^^^^^^^型注釈
if (num > 0) {
return "positive";
}
if (num < 0) {
return "negative";
}
// returnなしでもエラーにならない
}
ts
function negaposi(num: number): string | void {
// ^^^^^^^^^^^^^型注釈
if (num > 0) {
return "positive";
}
if (num < 0) {
return "negative";
}
// returnなしでもエラーにならない
}

voidがユニオン型に含まれない場合、たとえundefinedが含まれていても、何も返さない経路があるとエラーになります。

ts
function negaposi(num: number): string | undefined {
Not all code paths return a value.7030Not all code paths return a value.
if (num > 0) {
return "positive";
}
if (num < 0) {
return "negative";
}
// ここにreturnが必要
}
ts
function negaposi(num: number): string | undefined {
Not all code paths return a value.7030Not all code paths return a value.
if (num > 0) {
return "positive";
}
if (num < 0) {
return "negative";
}
// ここにreturnが必要
}

noImplicitReturnsの警告を抑えるために、戻り値の型をvoidと別の型で構成するよりも、しっかりreturn undefinedを書き、戻り値の型注釈もstring | undefinedとするほうが、意外性の少ないコードになるので、特に事情がない限りそう書くようにしましょう。

ts
function negaposi(num: number): string | undefined {
if (num > 0) {
return "positive";
}
if (num < 0) {
return "negative";
}
return undefined;
}
ts
function negaposi(num: number): string | undefined {
if (num > 0) {
return "positive";
}
if (num < 0) {
return "negative";
}
return undefined;
}

最後に、戻り値の型注釈がanyの場合もreturnなしが許容されます。

ts
function log(message?: string): any {
// ^^^型注釈
if (!message) {
return;
}
console.log(message);
// returnなしでもエラーにならない
}
ts
function log(message?: string): any {
// ^^^型注釈
if (!message) {
return;
}
console.log(message);
// returnなしでもエラーにならない
}

注意点としてanynoImplicitReturnsの警告を抑えるだけでなく、他の型チェックも放棄するので、コードの安全性を損なう可能性があります。特段の理由がない限り、noImplicitReturnsについての警告を抑えるためだけにanyを使うのは避けましょう。