- TypeScript
JSDocでTypeScript型チェック|@type・@paramタグ一覧
JSDocで型を書いた JavaScript は、TypeScript のコンパイラ(tsc)でそのまま型チェックできます。 ファイルの拡張子は .js のままで、/** @type {string} */ のようなコメントが型注釈の役目をします。ビルドの手順を増やさずに、打ち間違いや型の食い違いを事前に見つけられます。
この記事では、TypeScript が解釈する JSDoc タグを一覧表にまとめ、すべてのタグを .js ファイルに書いて tsc --checkJs --noEmit にかけた結果(TS番号つきのエラー全文)を載せます。TypeScript 7.0 で扱いが変わった @enum と @constructor の置き換え方、// @ts-check・checkJs・jsconfig.json の違い、.js から .d.ts を作るコマンドも扱います。
確認環境:Ubuntu 24.04、Node.js 22.22.2、TypeScript 7.0.2。版による違いは TypeScript 5.9.3・5.4.5・4.9.5 を別フォルダに入れて比べました。
JSDocタグの早見表
| タグ | 書き方 | 相当する TS 構文 | 確かめた版 |
|---|---|---|---|
@type |
/** @type {string} */ let s; |
let s: string |
4.9.5〜7.0.2 |
@type(キャスト) |
/** @type {Item} */ (value) |
value as Item |
4.9.5〜7.0.2 |
@param |
@param {number} [rate=0.1] 説明 |
(rate: number = 0.1) |
4.9.5〜7.0.2 |
@returns |
@returns {number} |
): number |
4.9.5〜7.0.2 |
@typedef |
@typedef {object} Item + @property |
type Item = {...} |
4.9.5〜7.0.2 |
@property |
@property {number} [qty] |
qty?: number |
4.9.5〜7.0.2 |
@callback |
@callback Fn + @param @returns |
type Fn = (x: A) => B |
4.9.5〜7.0.2 |
@template |
@template T |
型引数 T を持つ関数・クラス |
4.9.5〜7.0.2 |
@satisfies |
/** @satisfies {T} */ ({...}) |
{...} satisfies T |
5.0 以降(4.9.5 では検査されない) |
@overload |
@overload を並べたコメント |
関数のオーバーロード | 5.0 以降(4.9.5 では検査されない) |
@import |
/** @import { Item } from "./types.js" */ |
import type { Item } from |
5.5 以降(5.4.5 では TS2552) |
@this |
@this {HTMLElement} |
function (this: HTMLElement) |
4.9.5〜7.0.2 |
@extends |
/** @extends {Box<number>} */ |
型引数つきの extends |
4.9.5〜7.0.2 |
@implements |
/** @implements {Printable} */ |
implements Printable |
4.9.5〜7.0.2 |
@readonly @private @protected @public |
プロパティの直前に書く | 同名の修飾子 | 4.9.5〜7.0.2 |
@override |
メソッドの直前に書く | override |
4.9.5〜7.0.2 |
@deprecated |
/** @deprecated 代わりの名前 */ |
同じ(.ts でも使える) | 4.9.5〜7.0.2 |
@enum |
/** @enum {number} */ const P = {...} |
値の型をそろえたオブジェクト | 5.9.3 まで。7.0.2 では @typedef に置き換える |
@constructor / @class |
関数をクラスとして扱う | class |
5.9.3 まで。7.0.2 では class に置き換える |
「確かめた版」は、後で載せるエラー用サンプル(ng.js)を各版の tsc にかけ、そのタグのエラーが出ることを見た結果です(4.9.5 と 5.4.5 では @import を @typedef {import(...)} に書き換えて実行)。@satisfies と @overload は TypeScript 5.0、@import は 5.5 のリリースノートに追加の記載があります。
型チェックを有効にする方法は3つあります。
| 方法 | 書く場所 | 対象 |
|---|---|---|
// @ts-check |
.js ファイルの先頭 | そのファイルだけ |
--checkJs("checkJs": true) |
コマンドまたは設定ファイル | 対象の .js すべて |
| jsconfig.json | フォルダの直下 | エディタ用。tsc では -p jsconfig.json で指定 |
本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。
(記載例)出典:株式会社RJC「JSDocでTypeScript型チェック|@type・@paramタグ一覧」
最小の手順:3行で試す
npm init -y
npm i -D typescript
npx tsc --checkJs --noEmit plain.js
// plain.js
/** @type {number} */
let count = "3";
function add(a, b) {
return a + b;
}
add(1);
plain.js(2,5): error TS2322: Type 'string' is not assignable to type 'number'.
plain.js(4,14): error TS7006: Parameter 'a' implicitly has an 'any' type.
plain.js(4,17): error TS7006: Parameter 'b' implicitly has an 'any' type.
--noEmit は「ファイルを出力せず、チェックだけする」指定です。TypeScript 7.0.2 は strict が既定で有効なので、型を書いていない引数(a と b)も TS7006 として知らせてくれます。
タグを全部使ったサンプル(エラー0件)
型の定義だけを集めた types.js と、それを使う ok.js です。2つを同じフォルダ(src)に置きます。
// src/types.js
/**
* @typedef {object} Item
* @property {string} name 品名
* @property {number} price 単価
* @property {number} [qty] 数量(省略可)
*/
/** @typedef {"todo" | "doing" | "done"} Status */
/**
* @callback PriceFilter
* @param {Item} item
* @returns {boolean}
*/
export {};
// src/ok.js
/** @import { Item, Status, PriceFilter } from "./types.js" */
// @type:変数の型
/** @type {string} */
let title = "備品リスト";
/** @type {Item[]} */
const items = [
{ name: "ノート", price: 300, qty: 2 },
{ name: "ペン", price: 120 },
];
// @param / @returns:引数と戻り値
/**
* @param {Item[]} list
* @param {number} [taxRate=0.1] 税率(省略可)
* @returns {number}
*/
function total(list, taxRate = 0.1) {
const sum = list.reduce((acc, i) => acc + i.price * (i.qty ?? 1), 0);
return Math.round(sum * (1 + taxRate));
}
// @callback で定義した関数型を @type で使う
/** @type {PriceFilter} */
const isCheap = (item) => item.price < 200;
// @template:ジェネリクス
/**
* @template T
* @param {T[]} list
* @returns {T | undefined}
*/
function first(list) {
return list[0];
}
// @satisfies:型に合うか確かめつつ、推論された型を保つ
const labels = /** @satisfies {Record<Status, string>} */ ({
todo: "未着手",
doing: "作業中",
done: "完了",
});
// @overload:呼び出し方ごとの型
/**
* @overload
* @param {string} value
* @returns {string}
*/
/**
* @overload
* @param {number} value
* @returns {number}
*/
/**
* @param {string | number} value
* @returns {string | number}
*/
function double(value) {
return typeof value === "string" ? value + value : value * 2;
}
// 定数の集まりを型にする(as const と typeof の組み合わせ)
const Priority = /** @type {const} */ ({ LOW: 1, MIDDLE: 2, HIGH: 3 });
/** @typedef {typeof Priority[keyof typeof Priority]} PriorityValue */
/** @param {PriorityValue} p */
function priorityLabel(p) {
return p === Priority.HIGH ? "高" : "通常";
}
// @this:this の型
/**
* @this {HTMLElement}
* @param {Event} e
*/
function onClick(e) {
console.log(this.tagName, e.type);
}
// クラス:@extends・@implements・アクセス修飾子・@override
/**
* @template T
*/
class Box {
/** @param {T} value */
constructor(value) {
/** @readonly */
this.value = value;
/** @private */
this.secret = "s";
/** @protected */
this.count = 0;
}
/** @returns {string} */
describe() {
return `Box(${this.value})`;
}
}
/** @extends {Box<number>} */
class NumberBox extends Box {
/** @override */
describe() {
return `NumberBox(${this.value.toFixed(1)})`;
}
}
/** @typedef {{ print(): string }} Printable */
/** @implements {Printable} */
class Report {
print() {
return "report";
}
}
// 型アサーション(キャスト)
const raw = /** @type {unknown} */ (JSON.parse('{"name":"消しゴム","price":80}'));
const parsed = /** @type {Item} */ (raw);
/** @deprecated total を使ってください */
function sum(/** @type {number[]} */ nums) {
return nums.reduce((a, b) => a + b, 0);
}
console.log(title, total(items), items.filter(isCheap).length, first(items)?.name);
console.log(labels.done, double("ab"), double(21), priorityLabel(Priority.HIGH));
console.log(new NumberBox(3).describe(), new Report().print(), parsed.name, sum([1, 2]));
console.log(typeof onClick);
設定ファイル(jsconfig.json)と、package.json です。
{
"compilerOptions": {
"target": "es2022",
"module": "nodenext",
"checkJs": true,
"strict": true,
"noEmit": true
},
"include": ["src"]
}
{ "type": "module" }
$ npx tsc -p jsconfig.json
$ node src/ok.js
備品リスト 792 1 ノート
完了 abab 42 高
NumberBox(3.0) report 消しゴム 3
function
tsc は何も表示せずに終わり(エラー0件)、同じファイルを Node.js でそのまま実行できました。コメントで型を書いているだけなので、変換の工程が要らないのが JSDoc 方式の持ち味です。
各タグの要点
| タグ | 要点 |
|---|---|
@type |
変数の直前に書く。式の前に書いて丸かっこで囲むとキャストになる |
@param |
[名前] で省略可、[名前=既定値] で既定値つき。{...number} で可変長、options.name の形で分割代入の中身も書ける |
@typedef |
型に名前を付ける。{object} + @property の形か、{"a" | "b"} のように TS の型をそのまま書く |
@callback |
関数の型に名前を付ける。@type {(a: number) => number} と直接書いてもよい |
@template |
型引数を宣言する。クラスに付けるとクラス全体で使える |
@satisfies |
型に合っているか確かめるだけで、変数の型は推論されたまま残る |
@overload |
1つのコメントが1つの呼び出し方。最後のコメントが実装の型 |
@import |
型だけを読み込む。コメントなので実行時には何も読み込まれない |
分割代入・可変長引数・Promise の例も、動く形で載せておきます。
// src/more.js
// @ts-check
/** @typedef {import("./types.js").Item} Item */
/**
* @param {object} options
* @param {string} options.name 名前
* @param {number} [options.limit] 上限(省略可)
* @param {...number} scores 点数(可変長)
* @returns {Promise<Item>}
*/
async function build({ name, limit = 10 }, ...scores) {
const price = Math.min(limit, scores.reduce((a, b) => a + b, 0));
return { name, price };
}
/** @type {(a: number, b: number) => number} */
const add = (a, b) => a + b;
/** @type {Map<string, Item>} */
const stock = new Map();
build({ name: "ノート" }, 3, 4).then((item) => {
stock.set(item.name, item);
console.log(item, add(1, 2), stock.size);
});
$ npx tsc --checkJs --noEmit --strict --target es2022 --module nodenext src/more.js
$ node src/more.js
{ name: 'ノート', price: 7 } 3 1
1行目の @typedef {import("./types.js").Item} Item は、@import が入る前から使える書き方です。TypeScript 5.5 のリリースノートでは、この書き方を置き換えるものとして @import が紹介されています。
型エラーの全文:タグごとにわざと間違える
次の ng.js は、タグごとに1か所ずつ型に合わないコードを書いたものです。import している lib.js は「.jsから.d.tsを生成する」の節に全文があります。
// src/ng.js
/** @import { Item, Status, PriceFilter } from "./types.js" */
import { Box, NumberBox, total, first, double } from "./lib.js";
/** @type {string} */
let title = 123;
/** @type {Item} */
const item = { name: "ノート", price: "300" };
total([item], "0.1");
/** @type {PriceFilter} */
const isCheap = (item) => item.price;
/** @type {string | undefined} */
const s = first([1, 2, 3]);
const labels = /** @satisfies {Record<Status, string>} */ ({
todo: "未着手",
doing: "作業中",
});
double(true);
const box = new NumberBox(3);
box.value = 5;
box.secret;
box.count;
/** @typedef {{ print(): string }} Printable */
/** @implements {Printable} */
class Report {}
class Child extends Box {
/** @override */
descride() {
return "";
}
}
/**
* @this {HTMLElement}
*/
function onClick() {
console.log(this.tagNme);
}
/**
* @param {string} nmae
* @returns {number}
*/
function len(name) {
return name;
}
/** @type {Status} */
const st = "finished";
$ npx tsc -p jsconfig.json
src/ng.js(5,5): error TS2322: Type 'number' is not assignable to type 'string'.
src/ng.js(8,29): error TS2322: Type 'string' is not assignable to type 'number'.
src/ng.js(10,15): error TS2345: Argument of type 'string' is not assignable to parameter of type 'number'.
src/ng.js(13,27): error TS2322: Type 'number' is not assignable to type 'boolean'.
src/ng.js(16,7): error TS2322: Type 'number | undefined' is not assignable to type 'string | undefined'.
Type 'number' is not assignable to type 'string'.
src/ng.js(18,21): error TS2741: Property 'done' is missing in type '{ todo: string; doing: string; }' but required in type 'Record<Status, string>'.
src/ng.js(23,8): error TS2769: No overload matches this call.
The last overload gave the following error.
Argument of type 'boolean' is not assignable to parameter of type 'number'.
src/ng.js(26,5): error TS2540: Cannot assign to 'value' because it is a read-only property.
src/ng.js(27,5): error TS2341: Property 'secret' is private and only accessible within class 'Box<T>'.
src/ng.js(28,5): error TS2445: Property 'count' is protected and only accessible within class 'Box<T>' and its subclasses.
src/ng.js(32,7): error TS2420: Class 'Report' incorrectly implements interface 'Printable'.
Property 'print' is missing in type 'Report' but required in type 'Printable'.
src/ng.js(34,21): error TS8026: Expected Box<T> type arguments; provide these with an '@extends' tag.
src/ng.js(36,3): error TS4123: This member cannot have a JSDoc comment with an 'override' tag because it is not declared in the base class 'Box<any>'. Did you mean 'describe'?
src/ng.js(45,20): error TS2551: Property 'tagNme' does not exist on type 'HTMLElement'. Did you mean 'tagName'?
src/ng.js(49,20): error TS8024: JSDoc '@param' tag has name 'nmae', but there is no parameter with that name.
src/ng.js(52,14): error TS7006: Parameter 'name' implicitly has an 'any' type.
src/ng.js(57,7): error TS2322: Type '"finished"' is not assignable to type 'Status'.
| 行 | タグ | TS番号 | 何を知らせているか |
|---|---|---|---|
| 5 | @type |
TS2322 | string の変数に number を入れた |
| 8 | @typedef |
TS2322 | price に文字列を入れた |
| 10 | @param |
TS2345 | 引数 taxRate に文字列を渡した |
| 13 | @callback |
TS2322 | boolean を返すはずが number を返した |
| 16 | @template |
TS2322 | first([1, 2, 3]) の戻り値は number | undefined |
| 18 | @satisfies |
TS2741 | done が足りない |
| 23 | @overload |
TS2769 | boolean を受け取る呼び出し方が無い |
| 26 | @readonly |
TS2540 | 読み取り専用のプロパティに代入した |
| 27 | @private |
TS2341 | クラスの外から private に触れた |
| 28 | @protected |
TS2445 | クラスの外から protected に触れた |
| 32 | @implements |
TS2420 | print メソッドが無い |
| 34 | @extends |
TS8026 | 型引数つきのクラスを継承するときは @extends で型引数を書く |
| 36 | @override |
TS4123 | 親クラスに無い名前(describe の打ち間違い) |
| 45 | @this |
TS2551 | tagName の打ち間違い。候補まで出る |
| 49・52 | @param |
TS8024・TS7006 | コメントの引数名と実際の引数名が違う |
| 57 | @typedef |
TS2322 | Status に無い文字列 |
npx tsc -p jsconfig.json --locale ja を付けると日本語で表示されます。たとえば5行目と18行目は次のとおりです。
src/ng.js(5,5): error TS2322: 型 'number' を型 'string' に割り当てることはできません。
src/ng.js(18,21): error TS2741: プロパティ 'done' は型 '{ todo: string; doing: string; }' にありませんが、型 'Record<Status, string>' では必須です。
TS8024 は JSDoc ならではのエラーです。コメントの @param の名前が実際の引数名と合っていないと、その引数は型なしの扱いになり、続けて TS7006 も出ます。引数名を変えたときはコメントも一緒に直します。
TypeScript 7.0で扱いが変わったタグ:@enumと@constructor
@enum と @constructor は、TypeScript 5.9.3 と 7.0.2 で結果が変わりました。同じファイルを両方の版にかけます。
// src/legacy.js
// @ts-check
/** @enum {number} */
const Priority = { LOW: 1, MIDDLE: "2", HIGH: 3 };
/** @param {Priority} p */
function setPriority(p) {}
setPriority("HIGH");
/**
* @constructor
* @param {string} name
*/
function Person(name) {
this.name = name;
}
const person = new Person("佐藤");
person.nmae;
TypeScript 5.9.3 の結果:
src/legacy.js(3,28): error TS2322: Type 'string' is not assignable to type 'number'.
src/legacy.js(7,13): error TS2345: Argument of type 'string' is not assignable to parameter of type 'number'.
src/legacy.js(17,8): error TS2339: Property 'nmae' does not exist on type 'Person'.
TypeScript 7.0.2 の結果:
src/legacy.js(5,13): error TS2749: 'Priority' refers to a value, but is being used as a type here. Did you mean 'typeof Priority'?
src/legacy.js(14,3): error TS2683: 'this' implicitly has type 'any' because it does not have a type annotation.
src/legacy.js(16,16): error TS7009: 'new' expression, whose target lacks a construct signature, implicitly has an 'any' type.
| タグ | 5.9.3 | 7.0.2 |
|---|---|---|
@enum |
値の型をチェックし、型名としても使える | 型名として使うと TS2749。値の型(MIDDLE: "2")は指摘されない |
@constructor |
関数を new でき、プロパティもチェックされる |
TS2683 と TS7009 |
公式ブログの TypeScript 7.0 の告知にも、JavaScript ファイルの解析を TypeScript ファイルと同じ考え方にそろえた結果として、@enum は認識されなくなり @typedef を使うこと、@class を付けた関数はコンストラクタとして扱われなくなり class 宣言を使うことが書かれています。7.0.2 での置き換え方は次のとおりです。
// src/enum7.js
// @ts-check
const Priority = /** @type {const} */ ({ LOW: 1, MIDDLE: 2, HIGH: 3 });
/** @typedef {typeof Priority[keyof typeof Priority]} PriorityValue */
/** @param {PriorityValue} p */
function setPriority(p) {
console.log("priority:", p);
}
setPriority(Priority.HIGH);
setPriority(4);
class Person {
/** @param {string} name */
constructor(name) {
this.name = name;
}
}
new Person("佐藤").nmae;
$ npx tsc --checkJs --noEmit --strict --target es2022 --module nodenext src/enum7.js
src/enum7.js(10,13): error TS2345: Argument of type '4' is not assignable to parameter of type 'PriorityValue'.
src/enum7.js(18,18): error TS2339: Property 'nmae' does not exist on type 'Person'.
/** @type {const} */ (...) は TypeScript の as const にあたり、PriorityValue は 1 | 2 | 3 になります。5.9.3 の @enum {number} では number なら何でも通りましたが、この書き方は 定義した値だけを受け付けるので、setPriority(4) も知らせてくれます。
版による違い:@satisfies・@overload・@import
同じファイルを、以前の版の tsc にもかけて比べました。
// src/old.js
// @ts-check
const labels = /** @satisfies {Record<"todo" | "done", string>} */ ({
todo: "未着手",
});
/**
* @overload
* @param {string} value
* @returns {string}
*/
/**
* @overload
* @param {number} value
* @returns {number}
*/
/**
* @param {string | number} value
* @returns {string | number}
*/
function double(value) {
return typeof value === "string" ? value + value : value * 2;
}
/** @type {number} */
const n = double("ab");
| 版 | 2行目(@satisfies) |
24行目(@overload) |
|---|---|---|
| 4.9.5 | エラーなし(検査されない) | TS2322: Type 'string | number' is not assignable to type 'number' |
| 5.9.3 | TS1360(done が足りない) | TS2322: Type 'string' is not assignable to type 'number' |
| 7.0.2 | TS2741(done が足りない) | TS2322: Type 'string' is not assignable to type 'number' |
4.9.5 では @overload が読まれず、実装の型(string | number)がそのまま戻り値になっています。5.9.3 と 7.0.2 では「文字列を渡したら文字列が返る」ことまで伝わっています。7.0.2 の出力全文です。
src/old.js(2,21): error TS2741: Property 'done' is missing in type '{ todo: string; }' but required in type 'Record<"done" | "todo", string>'.
src/old.js(24,7): error TS2322: Type 'string' is not assignable to type 'number'.
@import は、次のファイルで比べました。
// src/imp.js
// @ts-check
/** @import { Item } from "./types.js" */
/** @type {Item} */
const item = { name: "ノート", price: 300 };
console.log(item.name);
| 版 | 結果 |
|---|---|
| 5.4.5 | src/imp.js(4,12): error TS2552: Cannot find name 'Item'. Did you mean 'item'? |
| 5.9.3 | エラーなし |
| 7.0.2 | エラーなし |
5.4 以前の TypeScript を使っている場合は、/** @typedef {import("./types.js").Item} Item */ と書けば同じことができます。
ts-check・checkJs・jsconfig.jsonの違い
最初の plain.js(// @ts-check なし)と、先頭に // @ts-check を足した checked.js、// @ts-nocheck を足した nocheck.js を、オプションを変えてチェックしました。
| コマンド | 対象 | 結果 |
|---|---|---|
tsc --noEmit plain.js |
何も付けない | TS6504(.js は対象外。allowJs を付けるよう案内される) |
tsc --allowJs --noEmit plain.js |
allowJs だけ | エラー0件(読み込むだけでチェックしない) |
tsc --allowJs --noEmit checked.js |
allowJs + // @ts-check |
3件(TS2322、TS7006 が2件) |
tsc --checkJs --noEmit plain.js |
checkJs | 3件(同上) |
tsc --allowJs --checkJs --strict false --noEmit plain.js |
checkJs + strict を外す | 1件(TS2322 のみ) |
tsc --checkJs --noEmit nocheck.js |
checkJs + // @ts-nocheck |
エラー0件 |
何も付けない場合のメッセージは次のとおりです(1件分)。
error TS6504: File 'plain.js' is a JavaScript file. Did you mean to enable the 'allowJs' option?
The file is in the program because:
Root file specified for compilation
| 指定 | 役割 | 使いどころ |
|---|---|---|
| allowJs | .js を tsc の対象に入れる(チェックはしない) | .ts と .js が混ざっている |
| checkJs | 対象の .js をすべてチェックする | 全体をチェックしたい |
// @ts-check |
checkJs なしでも、そのファイルだけチェックする | 1ファイルずつ広げたい |
// @ts-nocheck |
checkJs ありでも、そのファイルだけ外す | 生成されたファイルなど |
| strict | 型を書いていない引数(TS7006)なども知らせる | 型をしっかり書く方針のとき |
1ファイルずつ始めるなら // @ts-check、全体に広げるなら checkJs + 例外に // @ts-nocheck という使い分けができます。型を書く量を抑えて始めたいときは、strict を外すと「書いた型と食い違っている箇所」だけに絞れます。
jsconfig.jsonはtscでは-pで指定する
jsconfig.json は JavaScript プロジェクト用の設定ファイルで、中身の書き方は tsconfig.json と同じです。tsc を引数なしで実行したときに自動で読まれるのは tsconfig.json のほうで、jsconfig.json だけを置いたフォルダで npx tsc を実行すると、チェックは行われずヘルプが表示されました。-p で指定するとチェックされます。
$ npx tsc -p jsconfig.json
plain.js(2,5): error TS2322: Type 'string' is not assignable to type 'number'.
plain.js(4,14): error TS7006: Parameter 'a' implicitly has an 'any' type.
plain.js(4,17): error TS7006: Parameter 'b' implicitly has an 'any' type.
package.json の scripts に "typecheck": "tsc -p jsconfig.json" と書いておくと、npm run typecheck で呼べます。
1行だけ除外する:@ts-expect-errorと@ts-ignore
// sup.js
// @ts-check
/** @type {number} */
let a = 1;
// @ts-expect-error 文字列を入れるテスト
a = "x";
// @ts-ignore
a = "y";
// @ts-expect-error
a = 2;
$ npx tsc --allowJs --noEmit sup.js
sup.js(11,1): error TS2578: Unused '@ts-expect-error' directive.
| コメント | 次の行にエラーがあるとき | 次の行にエラーが無いとき |
|---|---|---|
// @ts-expect-error |
表示しない | TS2578 で知らせる |
// @ts-ignore |
表示しない | 何も起きない |
@ts-expect-error は、コードが直ってエラーが無くなると「もう要りません」と教えてくれるので、除外のコメントが残り続けません。
.jsから.d.tsを生成する
JSDoc を書いた .js からは、型定義ファイル(.d.ts)を tsc で作れます。ライブラリとして配るときに、利用する側の TypeScript が型を読めるようになります。
// src/lib.js
/** @import { Item } from "./types.js" */
/**
* @param {Item[]} list
* @param {number} [taxRate=0.1]
* @returns {number}
*/
export function total(list, taxRate = 0.1) {
const sum = list.reduce((acc, i) => acc + i.price * (i.qty ?? 1), 0);
return Math.round(sum * (1 + taxRate));
}
/**
* @template T
* @param {T[]} list
* @returns {T | undefined}
*/
export function first(list) {
return list[0];
}
/**
* @overload
* @param {string} value
* @returns {string}
*/
/**
* @overload
* @param {number} value
* @returns {number}
*/
/**
* @param {string | number} value
* @returns {string | number}
*/
export function double(value) {
return typeof value === "string" ? value + value : value * 2;
}
export const Priority = /** @type {const} */ ({ LOW: 1, MIDDLE: 2, HIGH: 3 });
/** @typedef {typeof Priority[keyof typeof Priority]} PriorityValue */
/**
* @template T
*/
export class Box {
/** @param {T} value */
constructor(value) {
/** @readonly */
this.value = value;
/** @private */
this.secret = "s";
/** @protected */
this.count = 0;
}
/** @returns {string} */
describe() {
return `Box(${this.value})`;
}
}
/** @extends {Box<number>} */
export class NumberBox extends Box {
/** @override */
describe() {
return `NumberBox(${this.value.toFixed(1)})`;
}
}
/** @deprecated total を使ってください */
export function sum(/** @type {number[]} */ nums) {
return nums.reduce((a, b) => a + b, 0);
}
npx tsc src/lib.js src/types.js --allowJs --declaration --emitDeclarationOnly --outDir dts --target es2022 --module nodenext
出力された dts/lib.d.ts:
/** @import { Item } from "./types.js" */
import type { Item } from "./types.js";
/**
* @param {Item[]} list
* @param {number} [taxRate=0.1]
* @returns {number}
*/
export declare function total(list: Item[], taxRate?: number): number;
/**
* @template T
* @param {T[]} list
* @returns {T | undefined}
*/
export declare function first<T>(list: T[]): T | undefined;
export declare function double(value: string): string;
export declare function double(value: number): number;
export declare const Priority: {
readonly LOW: 1;
readonly MIDDLE: 2;
readonly HIGH: 3;
};
export type PriorityValue = typeof Priority[keyof typeof Priority];
/** @typedef {typeof Priority[keyof typeof Priority]} PriorityValue */
/**
* @template T
*/
export declare class Box<T> {
/** @readonly */
value: T;
/** @private */
secret;
/** @protected */
count: number;
/** @param {T} value */
constructor(value: T);
/** @returns {string} */
describe(): string;
}
/** @extends {Box<number>} */
export declare class NumberBox extends Box<number> {
/** @override */
describe(): string;
}
/** @deprecated total を使ってください */
export declare function sum(/** @type {number[]} */ nums: number[]): number;
出力された dts/types.d.ts:
/**
* @typedef {object} Item
* @property {string} name 品名
* @property {number} price 単価
* @property {number} [qty] 数量(省略可)
*/
export type Item = {
/**
* 品名
*/
name: string;
/**
* 単価
*/
price: number;
/**
* 数量(省略可)
*/
qty?: number;
};
export type Status = "todo" | "doing" | "done";
export type PriceFilter = (item: Item) => boolean;
/** @typedef {"todo" | "doing" | "done"} Status */
/**
* @callback PriceFilter
* @param {Item} item
* @returns {boolean}
*/
export {};
@importはimport typeに、@template Tは型引数つきの宣言(firstやBoxの直後に T が付いた形)に変わっている@overloadの2つのコメントは、2行のexport declare function doubleになっている[taxRate=0.1]はtaxRate?: number、@property {number} [qty]はqty?: numberになっている@typedefと@callbackはexport typeになり、@propertyの説明文もコメントとして残っている
JSDocと.tsのどちらを選ぶか
| 状況 | 向いている方法 |
|---|---|
| ビルドなしで動かしたい小さなスクリプトや設定ファイル | JSDoc + // @ts-check |
| 既存の JavaScript に少しずつ型を足したい | JSDoc + checkJs(strict は後から) |
| 型をたくさん定義する/ジェネリクスを多用する | .ts(型の記述が短く済む) |
| 新しく始める TypeScript 前提の開発 | .ts |
JSDoc で書いた型は .d.ts にもそのまま変換できるので、あとから .ts へ移す場合も、書いた型の情報はそのまま活かせます。
よくある質問
Q. JSDoc の型チェックに TypeScript のインストールは必要ですか?
A. コマンドでチェックするには npm i -D typescript で tsc を入れます。.js ファイル自体は変換せずにそのまま動くので、実行時に TypeScript は要りません。
Q. // @ts-check と checkJs はどちらを使えばよいですか?
A. 1ファイルずつ試すなら // @ts-check、フォルダ全体をチェックするなら設定ファイルに "checkJs": true を書きます。両方を同時に使っても問題ありません。
Q. @enum を書いたら TS2749 になりました。
A. TypeScript 7.0.2 では @enum が型名として扱われません。/** @type {const} */ ({...}) で定数を作り、@typedef {typeof X[keyof typeof X]} で型を作る形に置き換えると、定義した値だけを受け付ける型になります。
Q. 型を別ファイルにまとめるにはどうしますか?
A. @typedef と @callback を1つの .js にまとめ、最後に export {}; を書きます。使う側は /** @import { Item } from "./types.js" */ で読み込みます(TypeScript 5.5 以降)。
まとめ
- JSDoc で型を書いた .js は
tsc --checkJs --noEmitでそのまま型チェックできる - 基本は
@type・@param・@returns・@typedefの4つ。関数の型は@callback、ジェネリクスは@template @satisfiesと@overloadは 5.0 以降、@importは 5.5 以降。以前の版では検査されないかエラーになる@enumと@constructorは 7.0.2 で扱いが変わった。@typedefとclassに置き換える- 1ファイルなら
// @ts-check、全体なら checkJs。jsconfig.json はtsc -p jsconfig.jsonで指定する - コメントの引数名が実際と違うと TS8024。引数名を変えたらコメントも直す
tsc --allowJs --declaration --emitDeclarationOnlyで .js から .d.ts を作れる
参考・出典
- TypeScript 公式ハンドブック「JSDoc Reference」 https://www.typescriptlang.org/docs/handbook/jsdoc-supported-types.html
- TypeScript 公式ハンドブック「JS Projects Utilizing TypeScript」 https://www.typescriptlang.org/docs/handbook/intro-to-js-ts.html
- TypeScript 5.0 リリースノート(@satisfies / @overload Support in JSDoc) https://www.typescriptlang.org/docs/handbook/release-notes/typescript-5-0.html
- TypeScript 5.5 リリースノート(The JSDoc @import Tag) https://www.typescriptlang.org/docs/handbook/release-notes/typescript-5-5.html
- Announcing TypeScript 7.0(TypeScript 公式ブログ) https://devblogs.microsoft.com/typescript/announcing-typescript-7-0/
本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。
(記載例)出典:株式会社RJC「JSDocでTypeScript型チェック|@type・@paramタグ一覧」
株式会社RJC ― SI事業・SES事業・AI駆動開発。RJCは一緒に成長を楽しめる会社です。
WE ARE HIRING
RJCで一緒に開発しながら、
成長を楽しみませんか?
RJCは、Web・モバイル・AIを活用した開発プロジェクトで、テックリードやPM・PMOも活躍するシステム開発会社です。会社を知る、待遇を確かめる、話を聞いてみる。気になるところ見てみてください!
- 127日年間休日
- 12時間平均残業時間
- 毎日ガチャ遊びココロも大切にする福利厚生。アマギフなどの賞品ラインナップ!
ほかにも、チケットレストラン、書籍読み放題、2年ごとの慰労報奨(休暇 or 金一封)、11期連続の黒字決算。