TypeScript/tsconfig.jsonのセマンティクス
はじめに
honey-css-modules でも tsconfig.json を解析することになった
tsc/tsserver が tsconfig.json を解析する挙動にできるだけ合わせたい
そこでその挙動がどうなってるのか調べておきたい
include オプション
includeオプションを省略すると、"include": ["**/*"]と書いた時と同じ挙動になる
includeオプションに含まれるファイルでも、node_modules/bower_components/jspm_packages配下のファイルはコンパイル対象から除外される
"include": []と書くと、node_modulesなどの配下のファイルが型チェックの範囲に含まれてしまう
"include": ["src"]と書いた時や、includeを省略した時はnode_modulesは除外されるが、1つもパスがない時は含まれてしまう
TypeScript のバグ...な気がするが"include": []と書くことないのでどうでも良すぎる
ts.readJsonConfigFile
tsConfigSourceFile.parseDiagnostics: 構文エラーの情報
internal API であり、型定義上は公開されていない
mizdra.icon ts.getConfigFileParsingDiagnosticsで取れるかも?
構文エラーがあっても、パースを継続して、それ以降のトークンもパースされる
愚直に JSONC としてパースするだけで、本来 string が来るべきところに number が来ていても、そのままパースされる
tsConfigSourceFile.extendedSourceFilesにextendsにリストされたファイルのパスが入ってる
ただし、ts.readJsonConfigFileを呼び出した直後の時点では入ってない
ts.parseJsonSourceFileConfigFileContentにtsConfigSourceFileを渡した時に代入される
extendedSourceFilesは絶対パスに解決されたものが入ってる
"extends": "@tsconfig/recommended/tsconfig.json"と書かれてたらextendedSourceFiles: ["/path/to/node_modules/@tsconfig/recommended/tsconfig.json"]になる
tsConfigSourceFile.configFileSpecsにはinclude/excludeを解析した情報が入ってる
ただし、ts.readJsonConfigFileを呼び出した直後の時点では入ってない
ts.parseJsonSourceFileConfigFileContentにtsConfigSourceFileを渡した時に代入される
internal API であり、型定義上は公開されていない
code:tsconfig.json
{
}
code:configFileSpecs.json
{
"validatedIncludeSpecs": "**/*", "validatedIncludeSpecsBeforeSubstitution": "**/*", "validatedExcludeSpecsBeforeSubstitution": "src/test", "isDefaultIncludeSpec": true
}
ts.parseJsonSourceFileConfigFileContent
parsedCommandLine.errors: セマンティックエラー
構文エラーは含まれない
parsedCommandLine.options: compilerOptionsに関するデータ
tsconfig.json のスキーマに一致しないものは省かれてる
無効なオプションは原則としてundefinedに fallback される
例: "module": 1=>"module": undefined
libのような配列の要素が無効な場合、配列から取り除かれる
例: "lib": ["esnext", 1]=>"lib": ["lib.esnext.d.ts"]
ただし"paths"は例外で、無効なデータが何故か含まれる
例: "paths": { "@/*": ["./*", 1], "#/*": 1 } => "paths": { "@/*": ["./*", 1], "#/*": 1 }
ts.ParsedCommandLine['options']['paths']型に一致してないので、おそらく TypeScript のバグ
code:tsconfig.json
{
"compilerOptions": {
"module": 1,
"paths": {
"@/*": 1,
"#/*": 1
}
},
"hcmOptions": {
"dtsOutDir": 1,
"arbitraryExtensions": 1
}
}
code:options.json
{
"module": undefined,
"paths": {
"@/*": 1,
"#/*": 1
},
"pathsBasePath": "/app",
"configFilePath": "/app/tsconfig.json"
}
parsedCommandLine.rawにtsconfig.jsonの生のパース結果が入ってる
hcmOptionsとかライブラリ拡張のオプションを解析したかったら、ここを見ると良い
code:tsconfig.json
{
"compilerOptions": {
"module": 1,
"paths": {
"@/*": 1,
"#/*": 1
}
},
"hcmOptions": {
"dtsOutDir": 1,
"arbitraryExtensions": 1
}
}
code:raw.json
{
"compilerOptions": {
"module": 1,
"paths": { "@/*": 1, "#/*": 1 }
},
"hcmOptions": { "dtsOutDir": 1, "arbitraryExtensions": 1 }
}
エラーのある tsconfig.json の扱い
tsserver は構文エラーやセマンティックエラーがあっても、エラーがある部分を無視して動いてくれる
構文エラーやセマンティックエラーは tsc によりコンソールに、tsserver によりエディタ上で報告される
extendsに指定されたファイルが存在しない、もしくは権限不足でアクセス不能の時、単にそのファイルからの値を引き継がない挙動となる
include/exclude オプションの正規化
TypeScript ではincludeが省略されたら、"include": ["**/*"]へと正規化する処理が内部的に行われる
この正規化を行う public API はない
include/exclude オプションの glob の展開
typescriptpackageからはts.matchFilesとして export されてるが...型定義上は存在せず、internal API になってる
glob にマッチするファイルのうち、ファイルシステムに存在するものなら、ts.sys.readDirectoryで取得可能
これは public API
あるファイルパスが glob にマッチするかを判定する public API はない
internal API のmatchFilesを使うしかない
extends オプションによるオプションの継承
extendsで指定されたファイルのオプションを、extends元のオプションで上書きする挙動となる
array や object のマージはされない
code:tsconfig.base.json
{
"compilerOptions": {
"target": "es2020",
"paths": {
"@/*": "./src/*",
}
}
}
code:tsconfig.json
{
"extends": "./tsconfig.base.json",
"compilerOptions": {
"paths": {
"#/*": "./src/*",
}
}
}
このtsconfig.jsonは以下と等価になる
code:tsconfig.resolved.json
{
"compilerOptions": {
"target": "es2020",
"paths": {
"#/*": "./src/*",
}
}
}
referencesはextendsにより引き継がれない唯一の例外であると、ドキュメントで触れられている
Currently, the only top-level property that is excluded from inheritance is references.
parsedCommandLine.optionsやtsConfigSourceFile.configFileSpecsに、引き継がれたあとの値が入ってる
ドキュメントでは触れられていないが、ライブラリの独自拡張のオプションは引き継がれない
そのため、vue language tools は自前で extends でvueCompilerOptionsを引き継ぐロジックを実装してる