1. 記事一覧 >
  2. ブログ記事
TypeScript
category logo

JS→ts-migrateでTypeScript化→ESLint導入 エラー対処メモ

(更新) (公開)

■はじめに

だいぶ前の話ですが、このブログを最初 JS で作成していて、途中から airbnb / ts-migrate を使って、TypeScript にしました。
ts-migrate の直後と ESLint 導入の直後は、エラー多数でボロボロでした。
その時の対処内容を備忘録的に書こうと思います。
(備忘録と言うからには、本当はその時書かないといけない話ですが、巻き戻して、当時のことも思い出して書きました。)


あくまで一例で、設定で抑えられるとか、他の修正方法とかの細かい話しは省略しています。

エラーは、英語表記で出力されるように設定しました。(日本語で出力すると、ググっても情報が少ないため。)

別の箇所にも発生した同じエラーや、ここに書いてあること以外にもエラーがあったり、修正することにより別のエラーになることもありましたが、省略しています。


■ts-migrate その1

とりあえず、pages 配下の .js に対して、ts-migrate (JS から TypeScript へ変換) してみました。

$ cd itc-blog
$ npm install --save-dev typescript @types/react @types/node
$ npm install --save-dev ts-migrate
$ find pages/ -type f
pages/index.js
pages/_app.js
pages/_document.js
pages/list/[[...slug]].js
pages/blogs/[id].js
$ npx ts-migrate-full pages
・・・
pages/_document.ts:45:1 - error TS1128: Declaration or statement expected.

45 }
   ~


Found 25 errors in the same file, starting at: pages/_document.ts:10

$ find pages/ -type f
pages/list/[[...slug]].tsx
pages/blogs/[id].tsx
pages/tsconfig.json
pages/_app.tsx
pages/_document.ts
pages/index.ts

エラーが出まくりましたが、.js が.tsx と.ts に変わりました。


中身を確認すると、多数エラーが発生していました。
ts-migrateその1 多数エラー


pages がソースコードのトップだと思って、相対パスで import していたところが全て
// @ts-expect-error ts-migrate(7016) FIXME: Could not find a declaration file for module '../.... Remove this comment to see the full error message
のエラーになり、話になりません。

相対パスでimport エラー


仕切り直しました。


Cannot use JSX...

エラー内容
Cannot use JSX unless the '--jsx' flag is provided. ts(17004)
Cannot use JSX unless the '--jsx' flag is provided. ts(17004)


原因
JSX 部分をどうするのかオプションで指定されていない。または、設定されていない。


修正方法
tsconfig.json"jsx": "preserve" を追加する。


修正例

tsconfig.json修正前
// "jsx": "preserve",                                /* Specify what JSX code is generated. */
tsconfig.json修正後
"jsx": "preserve",                                /* Specify what JSX code is generated. */

■ts-migrate その2

一旦元に戻して、以下のようにソースコードを src ディレクトリに入れて、一つにまとめてから仕切り直しました。

$ cd itc-blog
$ mkdir src
$ mv libs lib hooks components pages src
$ npm install --save-dev typescript @types/react @types/node
$ npm install --save-dev ts-migrate
$ npx ts-migrate init src

ここで、とりあえず、
src/tsconfig.json
が作成されて、何も行われません。
src/tsconfig.json を書き換えます。

$ vi src/tsconfig.json
src/tsconfig.json
// "jsx": "preserve",                                /* Specify what JSX code is generated. */
"jsx": "preserve",                                /* Specify what JSX code is generated. */
$ npx ts-migrate rename src
$ mv components/post/tweet.ts components/post/tweet.tsx
$ mv components/Jadate.ts     components/Jadate.tsx
$ mv pages/_document.ts       pages/_document.tsx

npx ts-migrate rename src.js -> .ts or .tsx にリネームされます。
画面(JSX)が関係すると、.tsx になりますが、そうならなかったファイルは、手動でリネームしました。


ソースコードを JS→TypeScript に書き換えます。

$ npx ts-migrate migrate src

エラーが検出された箇所は、 // @ts-expect-error ... とコメントが追加されますので、この部分を手動で対処していきます。


Unused...

エラー内容
Unused '@ts-expect-error' directive. ts(2578)
Unused '@ts-expect-error' directive. ts(2578)


原因
特に問題が無いのに、// @ts-expect-error が書かれている。


修正方法

// @ts-expect-error を書いたのは、ts-migrate。

今回の場合、srcに一旦ソースコードを移動して ts-migrate したため、依存関係がおかしくなり、同様のエラーが多発した。

// @ts-expect-error... を削除。


Could not find a declaration...

エラー内容
Could not find a declaration file for module 'react-syntax-highlighter'. '/opt/dev/itc-blog/node_modules/react-syntax-highlighter/dist/cjs/index.js' implicitly has an 'any' type.
Try `npm i --save-dev @types/react-syntax-highlighter` if it exists or add a new declaration (.d.ts) file containing `declare module 'react-syntax-highlighter';` ts(7016)
Could not find a declaration file for module 'react-syntax-highlighter'.


原因
型定義(.d.ts)が見つからない。


修正方法
型定義をインストール。(エラー文言に書いてある通り実行)

$ npm i --save-dev @types/react-syntax-highlighter

Property 'language'...

エラー内容
Property 'language' does not exist on type 'Readonly<{}> & Readonly<{ children?: ReactNode; }>'. ts(2339)
Property 'language' does not exist on type


原因
プロパティ(引数)の定義が無い。


修正方法
プロパティを定義して、FunctionComponent に書き換え。


修正例

修正前
export default class Code extends React.PureComponent {
  render() {
    // @ts-expect-error ts-migrate(2339) FIXME: Property 'language' does not exist on type 'Readon... Remove this comment to see the full error message
    const { language, value } = this.props;
    return (
      <SyntaxHighlighter
        language={(language === 'js' ? 'javascript' : language) || 'javascript'}
        style={okaidia}
      >
        {value}
      </SyntaxHighlighter>
    );
  }
}
修正後
interface Props {
  language: string;
  value: string;
}
const Code: FunctionComponent<Props> = ({ language, value }) => {
  return (
    <SyntaxHighlighter
      language={(language === 'js' ? 'javascript' : language) || 'javascript'}
      style={okaidia}
    >
      {value}
    </SyntaxHighlighter>
  );
};
export default Code;

Cannot find module...

エラー内容
Cannot find module '../settings.yml' or its corresponding type declarations. ts(2307)
Cannot find module '../settings.yml' or its corresponding type declarations. ts(2307)


原因
.ymlの型定義が無い。


修正方法

$ npx yaml-dts-gen settings.yml

で型定義(settings.d.ts)生成。
import '../settings.d.ts'
追加。


Object literal may...

エラー内容
Argument of type '{ headingsOffset: number; scrollSmoothOffset: number; tocSelector: string; contentSelector: string; headingSelector: string; hasInnerContainers: true; scrollSmooth: true; orderedList: false; }' is not assignable to parameter of type 'IStaticOptions'.
Object literal may only specify known properties, and 'scrollSmoothOffset' does not exist in type 'IStaticOptions'. ts(2345)
Object literal may only specify known properties, and 'scrollSmoothOffset' does not exist in type 'IStaticOptions'. ts(2345)


原因
node_modules/tocbot/index.d.tsscrollSmoothOffset?: number; が定義されていない。


修正方法
node_modules/tocbot/index.d.tsscrollSmoothOffset?: number; を定義。
実際行ったのは、
単純に古い tocbot がインストールされたため、最新になるようにインストールしなおし。

$ rm -r package-lock.json node_modules
$ npm install

is not assignable...

エラー内容
Argument of type '(argument: string, options?: { additionalDigits?: 0 | 1 | 2 | undefined; } | undefined) => Date' is not assignable to parameter of type 'number | Date'. ts(2345)
Jadate.tsx(6, 74): Did you mean to call this expression?
Argument of type...is not assignable to parameter of type 'number | Date'.ts (2345)


原因
引数がおかしい。


修正方法
number 型か Date 型を渡さないといけない。


修正例

修正前
return <time className="text-gray-600 text-center" dateTime={formatISO(parseISO: any(date))}>{format(parseISO: any(date), "yyyy年MM月dd日")}</time>;
修正後
return (
  <time className="text-gray-600 text-center" dateTime={formatISO(parseISO(date))}>{format(parseISO(date), "yyyy年MM月dd日")}</time>
);
//ts-migrateが良くない方向に変換していたので、元々の形に戻した。

',' expected...

エラー内容
',' expected. ts(1005)
',' expected. ts(1005)


原因
文法的に良く分からない位置に : がある。(, と間違っていないか?と言っている。)


修正方法
文法見直し。


修正例

修正前
return <time className="text-gray-600 text-center" dateTime={formatISO(parseISO: any(date))}>{format(parseISO: any(date), "yyyy年MM月dd日")}</time>;
修正後
return (
  <time className="text-gray-600 text-center" dateTime={formatISO(parseISO(date))}>{format(parseISO(date), "yyyy年MM月dd日")}</time>
);
//ts-migrateが良くない方向に変換していたので、元々の形に戻した。

'any' only refers...

エラー内容
'any' only refers to a type, but is being used as a value here. ts(2693)
'any' only refers to a type, but is being used as a value here. ts(2693)


原因
文法的に良く分からない位置に any 型の宣言がある。


修正方法
文法見直し。


修正例

修正前
return <time className="text-gray-600 text-center" dateTime={formatISO(parseISO: any(date))}>{format(parseISO: any(date), "yyyy年MM月dd日")}</time>;
修正後
return (
  <time className="text-gray-600 text-center" dateTime={formatISO(parseISO(date))}>{format(parseISO(date), "yyyy年MM月dd日")}</time>
);
//ts-migrateが良くない方向に変換していたので、元々の形に戻した。

Unexpected token...

エラー内容
Unexpected token. Did you mean `{'}'}` or `&rbrace;`? ts(1381)
Unexpected token. Did you mean... ts(1381)


原因
}の位置がおかしい。...というか、文法的におかしくなっていて、芋づる式に出ると思われる。


修正方法
文法見直し。


修正例

修正前
return <time className="text-gray-600 text-center" dateTime={formatISO(parseISO: any(date))}>{format(parseISO: any(date), "yyyy年MM月dd日")}</time>;
修正後
return (
  <time className="text-gray-600 text-center" dateTime={formatISO(parseISO(date))}>{format(parseISO(date), "yyyy年MM月dd日")}</time>
);
//ts-migrateが良くない方向に変換していたので、元々の形に戻した。

Property 'gtag'...

エラー内容
Property 'gtag' does not exist on type 'Window & typeof globalThis'. ts(2339)
Property 'gtag' does not exist on type... ts(2339)


原因
gtag が型定義されていない。


修正方法
gtag を型定義する。(@types/gtag.jsnpm install

$ npm install --save-dev @types/gtag.js

Expected 1 arguments...

エラー内容
Expected 1 arguments, but got 0. ts(2554)
index.d.ts(387, 9): An argument for 'defaultValue' was not provided.
Expected 1 arguments, but got 0. ts(2554)


原因
引数が必要なのに、渡していない。


修正方法
空のオブジェクトを渡す。


修正例

修正前
export const TweetsMap = createContext();
修正後
export const TweetsMap = createContext({});

This expression...

エラー内容
This expression is not callable.
Type '{}' has no call signatures. ts(2349)
This expression is not callable.


原因
関数として呼び出しできない。(該当関数は、実行時に定義される。)


修正方法
any 型にキャスト。


修正例

修正前
  if (addTweet) {
    addTweet(id);
    return { ignore: true };
  }
修正後
  if (addTweet) {
    (addTweet as any)(id);
    //any型にキャスト。エラーは出なくなったが、正直、良くない方法かも。
    return { ignore: true };
  }

Property 'opera'...

エラー内容
Property 'opera' does not exist on type 'Window & typeof globalThis'. ts(2339)
Property 'opera' does not exist on type...ts(2339)


原因
windowopera プロパティが定義されていない。


修正方法
any型にキャスト。


修正例

修正前
})(navigator.userAgent || navigator.vendor || window.opera);
修正後
})(navigator.userAgent || navigator.vendor || (window as any).opera);
//any型にキャスト。エラーは出なくなったが、正直、良くない方法かも。

refers to a UMD global...

エラー内容
'tocbot' refers to a UMD global, but the current file is a module. Consider adding an import instead. ts(2686)
Consider adding an import instead. ts(2686)


原因
tocbot(サードパーティーモジュール)を使うのに、import していない。


修正方法
サードパーティーモジュールを import する。


修正例

import tocbot from 'tocbot';

を追加


■ESLint 導入

$ npm install --save-dev ・・・ にて、いくつかパッケージを導入して、ESLint を導入しました。

package.json
  "devDependencies": {
    "@types/gtag.js": "0.0.4",
    "@types/node": "^15.3.0",
    "@types/react": "^17.0.6",
    "@types/react-dom": "^17.0.5",
    "@types/react-syntax-highlighter": "^13.5.0",
    "@typescript-eslint/eslint-plugin": "^4.24.0",
    "@typescript-eslint/parser": "^4.24.0",
    "eslint": "^7.26.0",
    "eslint-config-prettier": "^8.3.0",
    "eslint-plugin-react": "^7.23.2",
    "eslint-plugin-react-hooks": "^4.2.0",
    "patch-package": "^6.4.7",
    "prettier": "^2.3.0",
    "serve": "^11.3.2",
    "stylelint": "^13.13.1",
    "stylelint-config-recommended": "^5.0.0",
    "typescript": "^4.2.4"
  }

以降、この設定を有効にした段階で表示されたエラーへの対処記録になります。

.eslintrc.json
{
  "env": {
    "es6": true,
    "node": true,
    "browser": true,
    "commonjs": true
  },
  "parser": "@typescript-eslint/parser",
  "parserOptions": {
    "ecmaVersion": 2018,
    "ecmaFeatures": {
      "jsx": true
    },
    "sourceType": "module"
  },
  "settings": {
    "react": {
      "version": "detect"
    }
  },
  "plugins": ["react-hooks", "react", "@typescript-eslint"],
  "extends": [
    "eslint:recommended",
    "plugin:@typescript-eslint/eslint-recommended",
    "plugin:@typescript-eslint/recommended",
    "plugin:react/recommended",
    "plugin:react-hooks/recommended",
    "prettier"
  ],
  "rules": {
    "react/prop-types": "off",
    "@typescript-eslint/explicit-module-boundary-types": "off"
  },
  "overrides": [
    {
      "files": ["*.ts", "*.tsx"],
      "rules": {
        "@typescript-eslint/explicit-module-boundary-types": ["error"]
      }
    }
  ]
}

Missing return type...

エラー内容
Missing return type on function. eslint(@typescript-eslint/explicit-module-boundary-types)
Missing return type on function. eslint(@typescript-eslint/explicit-module-boundary-types)


原因
関数の return 値の型が指定されていない。


修正方法
関数の return 値の型を指定する。


修正例

修正前
const useMobileDevice = () => {
修正後
const useMobileDevice = (): [boolean] => {
//: [boolean]追加

修正前
export default function usePageView() {
修正後
export default function usePageView(): void {
//何も返さない場合、: void追加

Object pattern argument...

エラー内容
Object pattern argument should be typed with a non-any type. eslint(@typescript-eslint/explicit-module-boundary-types)
Object pattern argument should be typed with a non-any type. eslint(@typescript-eslint/explicit-module-boundary-types)


原因
any 型が指定されている。


修正方法
any 型でごまかさずに、ちゃんと型を指定する。


修正例

修正前
import React from 'react';
import Head from 'next/head';

export const Header = ({
  title
}: any) => {
修正後
import React, { FC } from 'react';
import Head from 'next/head';

interface Props {
  title: string;
}

export const Header: FC<Props> = ({ title }) => {
//any→Props

Unexpected any...

エラー内容
Unexpected any. Specify a different type. eslint(@typescript-eslint/no-explicit-any)
Unexpected any. Specify a different type. eslint(@typescript-eslint/no-explicit-any)


原因
any 型が指定されている。


修正方法
ちゃんと型を指定する。


修正例

修正前
import React from 'react';
import Head from 'next/head';

export const Header = ({
  title
}: any) => {
修正後
import React, { FC } from 'react';
import Head from 'next/head';

interface Props {
  title: string;
}

export const Header: FC<Props> = ({ title }) => {
//any→Props

is missing the..

エラー内容
Type '{ key: any; id: any; }' is missing the following properties from type 'Tweet': br, caption ts(2739)
Type...is missing the following properties from type 'Tweet': br, caption ts(2739)


原因
必須のプロパティが無い。


修正方法
必須のプロパティを指定する。


修正例

static tweet関連の実装を変えて対処したため、実際にはこの直し方ではない。

修正前
<Tweet key={value.twitter_id} id={value.twitter_id} />
修正後
<Tweet key={value.twitter_id} id={value.twitter_id} br={''} caption={''} />
//必須のプロパティ(Tweetコンポーネントの引数)を記述

is defined but...

エラー内容
'title' is defined but never used. eslint(@typescript-eslint/no-unused-vars)
is defined but never used. eslint(@typescript-eslint/no-unused-vars)


原因
引数を取って、その引数を使っていない。


修正方法
使っていない引数指定を削除する。


修正例

修正前
export const Footer = ({
  title
}: any) => {
修正後
export const Footer: FC = () => {
//({title}: any)→()

Using target...

エラー内容
Using target="_blank" without rel="noreferrer" (which implies rel="noopener") is a security risk in older browsers: see https://mathiasbynens.github.io/rel-noopener/#recommendations eslint(react/jsx-no-target-blank)
Using target=...recommendations (eslintreact/jsx-no-target-blank)


原因
target="_blank"を指定しているのに、rel="noreferrer"が指定されていない。(rel 属性に noreferrer を付けることで、参照先に対して参照元のリンクを渡さないようにする)


修正方法
target="_blank"と一緒にrel="noreferrer"を指定する。


修正例

修正前
    <a href={hatena_href} target="_blank" data-hatena-bookmark-title={hatena_title} data-tip="このページをはてなブックマークに追加する" onClick={handleClick}>
      <svg className="hatena-svg w-7 h-7 text-gray-500" fill="#6B7280" x="0px" y="0px"
        width="1024px" height="1024px" viewBox="0 0 1024 1024">
修正後
    <a
      href={hatena_href}
      target="_blank"
      data-hatena-bookmark-title={hatena_title}
      data-tip="このページをはてなブックマークに追加する"
      onClick={handleClick}
      rel="noreferrer">
      <svg
        className="hatena-svg w-7 h-7 text-gray-500"
        fill="#6B7280"
        x="0px"
        y="0px"
        width="1024px"
        height="1024px"
        viewBox="0 0 1024 1024">
//rel="noreferrer"を追加。

'React' must be...

エラー内容
'React' must be in scope when using JSX eslint(react/react-in-jsx-scope)
'React' must be in scope when using JSX eslint(react/react-in-jsx-scope)


原因
JSX を使っているのに React をインポートしていない。


修正方法
import React from 'react';を追加する。


修正例

修正前
import Document, { Html, Head, Main, NextScript } from 'next/document'
import { existsGaId, GA_ID } from '../lib/gtag'

export default class MyDocument extends Document {
  render() {
    return (
      <Html lang="ja">
修正後
import Document, { Html, Head, Main, NextScript } from 'next/document'
import { existsGaId, GA_ID } from '../lib/gtag'
import React from 'react';
//Reactのimport追加

export default class MyDocument extends Document {
  render() {
    return (
      <Html lang="ja">
//エラー箇所の修正は無し。

'React' must be...

エラー内容
Require statement not part of import statement. eslint(typescript-eslint/no-var-requires)
Require statement not part of import statement. eslint(typescript-eslint/no-var-requires)

原因
require ステートメントを使っている。


修正方法
// eslint-disable-next-line @typescript-eslint/no-var-requires で表示しないようにするか、
requireの代わりにawait importを使う。


修正例

修正前
const path = require('path');
修正後
// eslint-disable-next-line @typescript-eslint/no-var-requires
const path = require('path');

修正前
const path = require('path');
修正後
const path = await import('path');
//require→await import

loading...