diff --git a/README.md b/README.md index f273621e..a1bb4074 100644 --- a/README.md +++ b/README.md @@ -70,22 +70,23 @@ npm install --global @ysk8hori/typescript-graph ## Options -| Option | Description | -| ------------------------- | ------------------------------------------------------------------------------------------------------------------------------------ | -| `-V, --version` | Output the version number | -| `--md ` | Specify the name of the markdown file to be output. The default is typescript-graph.md. | -| `--mermaid-link` | (experimental) Generates a link on a node to open the corresponding file in VSCode. | -| `-d, --dir ` | Specifies the root directory of the TypeScript project to analyze. It reads and uses the tsconfig.json file found in this directory. | -| `--tsconfig ` | Specifies the path to the tsconfig file to use for analysis. If this option is provided, -d, --dir will be ignored. | -| `--include ` | Specify file paths or parts of file paths to include in the graph (relative to the tsconfig directory, without `./`). | -| `--exclude ` | Specify file paths or parts of file paths to exclude from the graph (relative to the tsconfig directory, without `./`). | -| `--abstraction ` | Specify the paths of directories to be abstracted. Abstracted directories are treated as a single node. | -| `--highlight ` | Specify the path and file names to be highlighted. | -| `--LR` | Set the flowchart orientation to Left-to-Right. | -| `--TB` | Set the flowchart orientation to Top-to-Bottom. | -| `--measure-instability` | Enable the beta feature to measure the instability of modules. | -| `--config-file` | Specify the relative path to the config file (from the current directory or as specified by -d, --dir). The default is .tsgrc.json. | -| `-h, --help` | Display help for the command. | +| Option | Description | +| ------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `-V, --version` | Output the version number | +| `--md ` | Specify the name of the markdown file to be output. The default is typescript-graph.md. | +| `--mermaid-link` | (experimental) Generates a link on a node to open the corresponding file in VSCode. | +| `-d, --dir ` | Specifies the root directory of the TypeScript project to analyze. It reads and uses the tsconfig.json file found in this directory. | +| `--tsconfig ` | Specifies the path to the tsconfig file to use for analysis. If this option is provided, -d, --dir will be ignored. | +| `--include ` | Specify file paths or parts of file paths to include in the graph (relative to the tsconfig directory, without `./`). | +| `--exclude ` | Specify file paths or parts of file paths to exclude from the graph (relative to the tsconfig directory, without `./`). | +| `--abstraction ` | Specify the paths of directories to be abstracted. Abstracted directories are treated as a single node. | +| `--highlight ` | Specify the path and file names to be highlighted. | +| `--LR` | Set the flowchart orientation to Left-to-Right. | +| `--TB` | Set the flowchart orientation to Top-to-Bottom. | +| `--measure-instability` | Enable the beta feature to measure the instability of modules. | +| `--config-file` | Specify the relative path to the config file (from the current directory or as specified by -d, --dir). The default is .tsgrc.json. | +| `--vue` (experimental) | `.vue` files are also included in the analysis. A temporary working directory is created using Node.js's `fs.mkdtempSync`, where all files targeted by `tsc` as well as `.vue` files are copied for processing. `.vue` files are renamed to `.vue.ts` unless a file with the same name already exists in the directory. | +| `-h, --help` | Display help for the command. | ## usage diff --git a/docs/README_en.md b/docs/README_en.md index f273621e..a1bb4074 100644 --- a/docs/README_en.md +++ b/docs/README_en.md @@ -70,22 +70,23 @@ npm install --global @ysk8hori/typescript-graph ## Options -| Option | Description | -| ------------------------- | ------------------------------------------------------------------------------------------------------------------------------------ | -| `-V, --version` | Output the version number | -| `--md ` | Specify the name of the markdown file to be output. The default is typescript-graph.md. | -| `--mermaid-link` | (experimental) Generates a link on a node to open the corresponding file in VSCode. | -| `-d, --dir ` | Specifies the root directory of the TypeScript project to analyze. It reads and uses the tsconfig.json file found in this directory. | -| `--tsconfig ` | Specifies the path to the tsconfig file to use for analysis. If this option is provided, -d, --dir will be ignored. | -| `--include ` | Specify file paths or parts of file paths to include in the graph (relative to the tsconfig directory, without `./`). | -| `--exclude ` | Specify file paths or parts of file paths to exclude from the graph (relative to the tsconfig directory, without `./`). | -| `--abstraction ` | Specify the paths of directories to be abstracted. Abstracted directories are treated as a single node. | -| `--highlight ` | Specify the path and file names to be highlighted. | -| `--LR` | Set the flowchart orientation to Left-to-Right. | -| `--TB` | Set the flowchart orientation to Top-to-Bottom. | -| `--measure-instability` | Enable the beta feature to measure the instability of modules. | -| `--config-file` | Specify the relative path to the config file (from the current directory or as specified by -d, --dir). The default is .tsgrc.json. | -| `-h, --help` | Display help for the command. | +| Option | Description | +| ------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `-V, --version` | Output the version number | +| `--md ` | Specify the name of the markdown file to be output. The default is typescript-graph.md. | +| `--mermaid-link` | (experimental) Generates a link on a node to open the corresponding file in VSCode. | +| `-d, --dir ` | Specifies the root directory of the TypeScript project to analyze. It reads and uses the tsconfig.json file found in this directory. | +| `--tsconfig ` | Specifies the path to the tsconfig file to use for analysis. If this option is provided, -d, --dir will be ignored. | +| `--include ` | Specify file paths or parts of file paths to include in the graph (relative to the tsconfig directory, without `./`). | +| `--exclude ` | Specify file paths or parts of file paths to exclude from the graph (relative to the tsconfig directory, without `./`). | +| `--abstraction ` | Specify the paths of directories to be abstracted. Abstracted directories are treated as a single node. | +| `--highlight ` | Specify the path and file names to be highlighted. | +| `--LR` | Set the flowchart orientation to Left-to-Right. | +| `--TB` | Set the flowchart orientation to Top-to-Bottom. | +| `--measure-instability` | Enable the beta feature to measure the instability of modules. | +| `--config-file` | Specify the relative path to the config file (from the current directory or as specified by -d, --dir). The default is .tsgrc.json. | +| `--vue` (experimental) | `.vue` files are also included in the analysis. A temporary working directory is created using Node.js's `fs.mkdtempSync`, where all files targeted by `tsc` as well as `.vue` files are copied for processing. `.vue` files are renamed to `.vue.ts` unless a file with the same name already exists in the directory. | +| `-h, --help` | Display help for the command. | ## usage diff --git a/docs/README_ja.md b/docs/README_ja.md index 2eb688dc..5c3278ff 100644 --- a/docs/README_ja.md +++ b/docs/README_ja.md @@ -70,22 +70,23 @@ npm install --global @ysk8hori/typescript-graph ## Options -| オプション | 説明 | -| ------------------------- | ---------------------------------------------------------------------------------------------------------------------------------- | -| `-V, --version` | バージョン番号を出力 | -| `--md ` | 出力するMarkdownファイルの名前を指定します。デフォルトは typescript-graph.md です。 | -| `--mermaid-link` | (experimental) ノードにリンクを生成し、そのファイルをVSCodeで開けるようにします。 | -| `-d, --dir ` | 解析対象のTypeScriptプロジェクトのルートディレクトリを指定します。そこにある tsconfig.json を読み取り、解析します。 | -| `--tsconfig ` | 解析に使用する tsconfig ファイルのパスを指定します。これが指定された場合、`-d, --dir` を無視します。 | -| `--include ` | グラフに含めるファイルパスやその一部を指定します(tsconfig ディレクトリからの相対パスで、`./`は不要です)。 | -| `--exclude ` | グラフから除外するファイルパスやその一部を指定します(tsconfig ディレクトリからの相対パスで、`./`は不要です)。 | -| `--abstraction ` | 抽象化したいディレクトリのパスを指定します。抽象化したディレクトリは一つのノードとして扱います。 | -| `--highlight ` | 強調表示するパスとファイル名を指定します。 | -| `--LR` | フローチャートの向きを左から右に指定します。 | -| `--TB` | フローチャートの向きを上から下に指定します。 | -| `--measure-instability` | モジュールの不安定性を測定するベータ機能を有効化します。 | -| `--config-file` | 設定ファイルへの相対パスを指定します(カレントディレクトリまたは -d, --dir で指定された場所から)。デフォルトは .tsgrc.json です。 | -| `-h, --help` | コマンドのヘルプを表示します。 | +| オプション | 説明 | +| ------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `-V, --version` | バージョン番号を出力 | +| `--md ` | 出力するMarkdownファイルの名前を指定します。デフォルトは typescript-graph.md です。 | +| `--mermaid-link` | (experimental) ノードにリンクを生成し、そのファイルをVSCodeで開けるようにします。 | +| `-d, --dir ` | 解析対象のTypeScriptプロジェクトのルートディレクトリを指定します。そこにある tsconfig.json を読み取り、解析します。 | +| `--tsconfig ` | 解析に使用する tsconfig ファイルのパスを指定します。これが指定された場合、`-d, --dir` を無視します。 | +| `--include ` | グラフに含めるファイルパスやその一部を指定します(tsconfig ディレクトリからの相対パスで、`./`は不要です)。 | +| `--exclude ` | グラフから除外するファイルパスやその一部を指定します(tsconfig ディレクトリからの相対パスで、`./`は不要です)。 | +| `--abstraction ` | 抽象化したいディレクトリのパスを指定します。抽象化したディレクトリは一つのノードとして扱います。 | +| `--highlight ` | 強調表示するパスとファイル名を指定します。 | +| `--LR` | フローチャートの向きを左から右に指定します。 | +| `--TB` | フローチャートの向きを上から下に指定します。 | +| `--measure-instability` | モジュールの不安定性を測定するベータ機能を有効化します。 | +| `--config-file` | 設定ファイルへの相対パスを指定します(カレントディレクトリまたは -d, --dir で指定された場所から)。デフォルトは .tsgrc.json です。 | +| `--vue` (experimental) | `.vue` ファイルも対象とします。Node.js の `fs.mkdtempSync` によって作業ディレクトリを作成し、そこへ tsc 対象となるファイルと `.vue` ファイルをコピーして解析します。`.vue` ファイルは `.vue.ts` へとリネームしますが、すでにそのファイルが存在する場合はリネームしません。 | +| `-h, --help` | コマンドのヘルプを表示します。 | ## 使い方 diff --git a/dummy_project/src/includeFiles/D.vue b/dummy_project/src/includeFiles/D.vue new file mode 100644 index 00000000..eba57358 --- /dev/null +++ b/dummy_project/src/includeFiles/D.vue @@ -0,0 +1,23 @@ + + + diff --git a/dummy_project/src/includeFiles/components/HelloWorld.vue b/dummy_project/src/includeFiles/components/HelloWorld.vue new file mode 100644 index 00000000..ba62c9cd --- /dev/null +++ b/dummy_project/src/includeFiles/components/HelloWorld.vue @@ -0,0 +1,41 @@ + + + + + diff --git a/dummy_project/src/main.ts b/dummy_project/src/main.ts index 57b25d8f..f92e6b60 100644 --- a/dummy_project/src/main.ts +++ b/dummy_project/src/main.ts @@ -5,6 +5,7 @@ import b2 from './otherFiles/e'; import a3 from './includeFiles/abstractions/j'; import b3 from './includeFiles/abstractions/k'; import ts from 'typescript'; +import D from './includeFiles/D.vue'; export default function main() { a(); diff --git a/integration.vue.spec.ts b/integration.vue.spec.ts new file mode 100644 index 00000000..0f43bd25 --- /dev/null +++ b/integration.vue.spec.ts @@ -0,0 +1,492 @@ +import { beforeAll, expect, test } from 'vitest'; +import { $ } from 'zx'; +import fs from 'fs'; +import path from 'path'; + +const dir = '__tmp__'; +const filename = 'test-vue.md'; +const filepath = path.join(dir, filename); + +beforeAll(() => { + if (!fs.existsSync(dir)) fs.mkdirSync(dir); +}); + +// TODO 現状、--config-file で指定するパスは -d からの相対パスだが、将来的には -d をなくしたい。その移行のため --config-file での指定は、-d を無視したカレントディレクトリからの相対パスで設定ファイルを優先し、なければ -d からの相対パスで探すようにしたい。 + +test('run:sample', async () => { + await $`ts-node -O '{\"module\": \"commonjs\"}' ./src/index.ts --tsconfig './dummy_project/tsconfig.json' --md ${filepath} --vue`; + + const file = fs.readFileSync(filepath, { encoding: 'utf-8' }); + expect(file).toMatchInlineSnapshot(` + "# TypeScript Graph + + \`\`\`bash + tsg --tsconfig ./dummy_project/tsconfig.json --md __tmp__/test-vue.md --vue + \`\`\` + + \`\`\`mermaid + flowchart + subgraph src["src"] + src/utils.ts["utils.ts"] + src/config.ts["config.ts"] + src/main.ts["main.ts"] + subgraph src/includeFiles["/includeFiles"] + src/includeFiles/b.ts["b.ts"] + src/includeFiles/c.ts["c.ts"] + src/includeFiles/a.ts["a.ts"] + src/includeFiles/D.vue["D.vue"] + subgraph src/includeFiles/children["/children"] + src/includeFiles/children/childA.ts["childA.ts"] + end + subgraph src/includeFiles/excludeFiles["/excludeFiles"] + src/includeFiles/excludeFiles/i.ts["i.ts"] + src/includeFiles/excludeFiles/g.ts["g.ts"] + src/includeFiles/excludeFiles/h.ts["h.ts"] + subgraph src/includeFiles/excludeFiles/style_["/style"] + src/includeFiles/excludeFiles/style_/style_.ts["style.ts"] + end + subgraph src/includeFiles/excludeFiles/class_["/class"] + src/includeFiles/excludeFiles/class_/class_A.ts["classA.ts"] + end + end + subgraph src/includeFiles/d["/d"] + src/includeFiles/d/d.ts["d.ts"] + src/includeFiles/d/index.ts["index.ts"] + end + subgraph src/includeFiles/abstractions["/abstractions"] + src/includeFiles/abstractions/j.ts["j.ts"] + src/includeFiles/abstractions/l.ts["l.ts"] + src/includeFiles/abstractions/k.ts["k.ts"] + subgraph src/includeFiles/abstractions/children["/children"] + src/includeFiles/abstractions/children/childA.ts["childA.ts"] + end + end + subgraph src/includeFiles/components["/components"] + src/includeFiles/components/HelloWorld.vue["HelloWorld.vue"] + end + end + subgraph src/otherFiles["/otherFiles"] + src/otherFiles/d.ts["d.ts"] + src/otherFiles/f.ts["f.ts"] + src/otherFiles/e.ts["e.ts"] + subgraph src/otherFiles/children["/children"] + src/otherFiles/children/:id.json[":id.json"] + src/otherFiles/children///id//.json["{id}.json"] + src/otherFiles/children/childA.ts["childA.ts"] + end + end + end + src/includeFiles/b.ts-->src/utils.ts + src/includeFiles/b.ts-->src/config.ts + src/includeFiles/c.ts-->src/utils.ts + src/includeFiles/c.ts-->src/includeFiles/b.ts + src/config.ts-->src/utils.ts + src/config.ts-->src/includeFiles/c.ts + src/includeFiles/children/childA.ts-->src/utils.ts + src/includeFiles/excludeFiles/i.ts-->src/utils.ts + src/includeFiles/d/index.ts-->src/includeFiles/d/d.ts + src/includeFiles/a.ts-->src/includeFiles/children/childA.ts + src/includeFiles/a.ts-->src/includeFiles/excludeFiles/g.ts + src/includeFiles/a.ts-->src/includeFiles/excludeFiles/i.ts + src/includeFiles/a.ts-->src/includeFiles/excludeFiles/style_/style_.ts + src/includeFiles/a.ts-->src/includeFiles/excludeFiles/class_/class_A.ts + src/includeFiles/a.ts-->src/includeFiles/excludeFiles/h.ts + src/includeFiles/a.ts-->src/includeFiles/d/index.ts + src/includeFiles/a.ts-->src/utils.ts + src/otherFiles/children/childA.ts-->src/utils.ts + src/otherFiles/children/childA.ts-->src/otherFiles/children/:id.json + src/otherFiles/children/childA.ts-->src/otherFiles/children///id//.json + src/otherFiles/d.ts-->src/otherFiles/children/childA.ts + src/otherFiles/d.ts-->src/utils.ts + src/otherFiles/f.ts-->src/utils.ts + src/otherFiles/e.ts-->src/otherFiles/f.ts + src/otherFiles/e.ts-->src/utils.ts + src/otherFiles/e.ts-->src/config.ts + src/includeFiles/abstractions/j.ts-->src/utils.ts + src/includeFiles/abstractions/j.ts-->src/includeFiles/abstractions/children/childA.ts + src/includeFiles/abstractions/j.ts-->data.json + src/includeFiles/abstractions/l.ts-->src/utils.ts + src/includeFiles/abstractions/k.ts-->src/includeFiles/abstractions/l.ts + src/includeFiles/abstractions/k.ts-->src/utils.ts + src/includeFiles/D.vue-->src/includeFiles/components/HelloWorld.vue + src/main.ts-->src/includeFiles/a.ts + src/main.ts-->src/includeFiles/b.ts + src/main.ts-->src/otherFiles/d.ts + src/main.ts-->src/otherFiles/e.ts + src/main.ts-->src/includeFiles/abstractions/j.ts + src/main.ts-->src/includeFiles/abstractions/k.ts + src/main.ts-->src/includeFiles/D.vue + src/main.ts-->src/utils.ts + \`\`\` + " + `); +}); + +test('run:sample:argument-include', async () => { + await $`ts-node -O '{\"module\": \"commonjs\"}' ./src/index.ts includeFiles config --tsconfig './dummy_project/tsconfig.json' --md ${filepath} --vue`; + + const file = fs.readFileSync(filepath, { encoding: 'utf-8' }); + expect(file).toMatchInlineSnapshot(` + "# TypeScript Graph + + \`\`\`bash + tsg includeFiles config --tsconfig ./dummy_project/tsconfig.json --md __tmp__/test-vue.md --vue + \`\`\` + + \`\`\`mermaid + flowchart + subgraph src["src"] + src/config.ts["config.ts"] + src/utils.ts["utils.ts"] + src/main.ts["main.ts"] + subgraph src/includeFiles["/includeFiles"] + src/includeFiles/b.ts["b.ts"] + src/includeFiles/c.ts["c.ts"] + src/includeFiles/a.ts["a.ts"] + src/includeFiles/D.vue["D.vue"] + subgraph src/includeFiles/children["/children"] + src/includeFiles/children/childA.ts["childA.ts"] + end + subgraph src/includeFiles/excludeFiles["/excludeFiles"] + src/includeFiles/excludeFiles/i.ts["i.ts"] + src/includeFiles/excludeFiles/g.ts["g.ts"] + src/includeFiles/excludeFiles/h.ts["h.ts"] + subgraph src/includeFiles/excludeFiles/style_["/style"] + src/includeFiles/excludeFiles/style_/style_.ts["style.ts"] + end + subgraph src/includeFiles/excludeFiles/class_["/class"] + src/includeFiles/excludeFiles/class_/class_A.ts["classA.ts"] + end + end + subgraph src/includeFiles/d["/d"] + src/includeFiles/d/d.ts["d.ts"] + src/includeFiles/d/index.ts["index.ts"] + end + subgraph src/includeFiles/abstractions["/abstractions"] + src/includeFiles/abstractions/j.ts["j.ts"] + src/includeFiles/abstractions/l.ts["l.ts"] + src/includeFiles/abstractions/k.ts["k.ts"] + subgraph src/includeFiles/abstractions/children["/children"] + src/includeFiles/abstractions/children/childA.ts["childA.ts"] + end + end + subgraph src/includeFiles/components["/components"] + src/includeFiles/components/HelloWorld.vue["HelloWorld.vue"] + end + end + subgraph src/otherFiles["/otherFiles"] + src/otherFiles/e.ts["e.ts"] + end + end + src/includeFiles/b.ts-->src/utils.ts + src/includeFiles/b.ts-->src/config.ts + src/includeFiles/c.ts-->src/utils.ts + src/includeFiles/c.ts-->src/includeFiles/b.ts + src/config.ts-->src/utils.ts + src/config.ts-->src/includeFiles/c.ts + src/includeFiles/children/childA.ts-->src/utils.ts + src/includeFiles/excludeFiles/i.ts-->src/utils.ts + src/includeFiles/d/index.ts-->src/includeFiles/d/d.ts + src/includeFiles/a.ts-->src/includeFiles/children/childA.ts + src/includeFiles/a.ts-->src/includeFiles/excludeFiles/g.ts + src/includeFiles/a.ts-->src/includeFiles/excludeFiles/i.ts + src/includeFiles/a.ts-->src/includeFiles/excludeFiles/style_/style_.ts + src/includeFiles/a.ts-->src/includeFiles/excludeFiles/class_/class_A.ts + src/includeFiles/a.ts-->src/includeFiles/excludeFiles/h.ts + src/includeFiles/a.ts-->src/includeFiles/d/index.ts + src/includeFiles/a.ts-->src/utils.ts + src/otherFiles/e.ts-->src/config.ts + src/includeFiles/abstractions/j.ts-->src/utils.ts + src/includeFiles/abstractions/j.ts-->src/includeFiles/abstractions/children/childA.ts + src/includeFiles/abstractions/j.ts-->data.json + src/includeFiles/abstractions/l.ts-->src/utils.ts + src/includeFiles/abstractions/k.ts-->src/includeFiles/abstractions/l.ts + src/includeFiles/abstractions/k.ts-->src/utils.ts + src/includeFiles/D.vue-->src/includeFiles/components/HelloWorld.vue + src/main.ts-->src/includeFiles/a.ts + src/main.ts-->src/includeFiles/b.ts + src/main.ts-->src/includeFiles/abstractions/j.ts + src/main.ts-->src/includeFiles/abstractions/k.ts + src/main.ts-->src/includeFiles/D.vue + src/otherFiles/e.ts-->src/utils.ts + src/main.ts-->src/otherFiles/e.ts + src/main.ts-->src/utils.ts + \`\`\` + " + `); +}); + +test('run:sample:include', async () => { + await $`ts-node -O '{\"module\": \"commonjs\"}' ./src/index.ts --tsconfig './dummy_project/tsconfig.json' --include includeFiles config --md ${filepath} --vue`; + + const file = fs.readFileSync(filepath, { encoding: 'utf-8' }); + expect(file).toMatchInlineSnapshot(` + "# TypeScript Graph + + \`\`\`bash + tsg --tsconfig ./dummy_project/tsconfig.json --include includeFiles config --md __tmp__/test-vue.md --vue + \`\`\` + + \`\`\`mermaid + flowchart + subgraph src["src"] + src/config.ts["config.ts"] + src/utils.ts["utils.ts"] + src/main.ts["main.ts"] + subgraph src/includeFiles["/includeFiles"] + src/includeFiles/b.ts["b.ts"] + src/includeFiles/c.ts["c.ts"] + src/includeFiles/a.ts["a.ts"] + src/includeFiles/D.vue["D.vue"] + subgraph src/includeFiles/children["/children"] + src/includeFiles/children/childA.ts["childA.ts"] + end + subgraph src/includeFiles/excludeFiles["/excludeFiles"] + src/includeFiles/excludeFiles/i.ts["i.ts"] + src/includeFiles/excludeFiles/g.ts["g.ts"] + src/includeFiles/excludeFiles/h.ts["h.ts"] + subgraph src/includeFiles/excludeFiles/style_["/style"] + src/includeFiles/excludeFiles/style_/style_.ts["style.ts"] + end + subgraph src/includeFiles/excludeFiles/class_["/class"] + src/includeFiles/excludeFiles/class_/class_A.ts["classA.ts"] + end + end + subgraph src/includeFiles/d["/d"] + src/includeFiles/d/d.ts["d.ts"] + src/includeFiles/d/index.ts["index.ts"] + end + subgraph src/includeFiles/abstractions["/abstractions"] + src/includeFiles/abstractions/j.ts["j.ts"] + src/includeFiles/abstractions/l.ts["l.ts"] + src/includeFiles/abstractions/k.ts["k.ts"] + subgraph src/includeFiles/abstractions/children["/children"] + src/includeFiles/abstractions/children/childA.ts["childA.ts"] + end + end + subgraph src/includeFiles/components["/components"] + src/includeFiles/components/HelloWorld.vue["HelloWorld.vue"] + end + end + subgraph src/otherFiles["/otherFiles"] + src/otherFiles/e.ts["e.ts"] + end + end + src/includeFiles/b.ts-->src/utils.ts + src/includeFiles/b.ts-->src/config.ts + src/includeFiles/c.ts-->src/utils.ts + src/includeFiles/c.ts-->src/includeFiles/b.ts + src/config.ts-->src/utils.ts + src/config.ts-->src/includeFiles/c.ts + src/includeFiles/children/childA.ts-->src/utils.ts + src/includeFiles/excludeFiles/i.ts-->src/utils.ts + src/includeFiles/d/index.ts-->src/includeFiles/d/d.ts + src/includeFiles/a.ts-->src/includeFiles/children/childA.ts + src/includeFiles/a.ts-->src/includeFiles/excludeFiles/g.ts + src/includeFiles/a.ts-->src/includeFiles/excludeFiles/i.ts + src/includeFiles/a.ts-->src/includeFiles/excludeFiles/style_/style_.ts + src/includeFiles/a.ts-->src/includeFiles/excludeFiles/class_/class_A.ts + src/includeFiles/a.ts-->src/includeFiles/excludeFiles/h.ts + src/includeFiles/a.ts-->src/includeFiles/d/index.ts + src/includeFiles/a.ts-->src/utils.ts + src/otherFiles/e.ts-->src/config.ts + src/includeFiles/abstractions/j.ts-->src/utils.ts + src/includeFiles/abstractions/j.ts-->src/includeFiles/abstractions/children/childA.ts + src/includeFiles/abstractions/j.ts-->data.json + src/includeFiles/abstractions/l.ts-->src/utils.ts + src/includeFiles/abstractions/k.ts-->src/includeFiles/abstractions/l.ts + src/includeFiles/abstractions/k.ts-->src/utils.ts + src/includeFiles/D.vue-->src/includeFiles/components/HelloWorld.vue + src/main.ts-->src/includeFiles/a.ts + src/main.ts-->src/includeFiles/b.ts + src/main.ts-->src/includeFiles/abstractions/j.ts + src/main.ts-->src/includeFiles/abstractions/k.ts + src/main.ts-->src/includeFiles/D.vue + src/otherFiles/e.ts-->src/utils.ts + src/main.ts-->src/otherFiles/e.ts + src/main.ts-->src/utils.ts + \`\`\` + " + `); +}); + +test('run:sample:exclude', async () => { + await $`ts-node -O '{\"module\": \"commonjs\"}' ./src/index.ts --tsconfig './dummy_project/tsconfig.json' --include includeFiles config --exclude excludeFiles utils --md ${filepath} --vue`; + + const file = fs.readFileSync(filepath, { encoding: 'utf-8' }); + expect(file).toMatchInlineSnapshot(` + "# TypeScript Graph + + \`\`\`bash + tsg --tsconfig ./dummy_project/tsconfig.json --include includeFiles config --exclude excludeFiles utils --md __tmp__/test-vue.md --vue + \`\`\` + + \`\`\`mermaid + flowchart + subgraph src["src"] + src/config.ts["config.ts"] + src/main.ts["main.ts"] + subgraph src/includeFiles["/includeFiles"] + src/includeFiles/b.ts["b.ts"] + src/includeFiles/c.ts["c.ts"] + src/includeFiles/a.ts["a.ts"] + src/includeFiles/D.vue["D.vue"] + subgraph src/includeFiles/children["/children"] + src/includeFiles/children/childA.ts["childA.ts"] + end + subgraph src/includeFiles/d["/d"] + src/includeFiles/d/d.ts["d.ts"] + src/includeFiles/d/index.ts["index.ts"] + end + subgraph src/includeFiles/abstractions["/abstractions"] + src/includeFiles/abstractions/j.ts["j.ts"] + src/includeFiles/abstractions/l.ts["l.ts"] + src/includeFiles/abstractions/k.ts["k.ts"] + subgraph src/includeFiles/abstractions/children["/children"] + src/includeFiles/abstractions/children/childA.ts["childA.ts"] + end + end + subgraph src/includeFiles/components["/components"] + src/includeFiles/components/HelloWorld.vue["HelloWorld.vue"] + end + end + subgraph src/otherFiles["/otherFiles"] + src/otherFiles/e.ts["e.ts"] + end + end + src/includeFiles/b.ts-->src/config.ts + src/includeFiles/c.ts-->src/includeFiles/b.ts + src/config.ts-->src/includeFiles/c.ts + src/includeFiles/d/index.ts-->src/includeFiles/d/d.ts + src/includeFiles/a.ts-->src/includeFiles/children/childA.ts + src/includeFiles/a.ts-->src/includeFiles/d/index.ts + src/otherFiles/e.ts-->src/config.ts + src/includeFiles/abstractions/j.ts-->src/includeFiles/abstractions/children/childA.ts + src/includeFiles/abstractions/j.ts-->data.json + src/includeFiles/abstractions/k.ts-->src/includeFiles/abstractions/l.ts + src/includeFiles/D.vue-->src/includeFiles/components/HelloWorld.vue + src/main.ts-->src/includeFiles/a.ts + src/main.ts-->src/includeFiles/b.ts + src/main.ts-->src/includeFiles/abstractions/j.ts + src/main.ts-->src/includeFiles/abstractions/k.ts + src/main.ts-->src/includeFiles/D.vue + src/main.ts-->src/otherFiles/e.ts + \`\`\` + " + `); +}); + +test('run:sample:abstraction', async () => { + await $`ts-node -O '{\"module\": \"commonjs\"}' ./src/index.ts --tsconfig './dummy_project/tsconfig.json' --include includeFiles config --exclude excludeFiles utils --abstraction abstractions --md ${filepath} --vue`; + + const file = fs.readFileSync(filepath, { encoding: 'utf-8' }); + expect(file).toMatchInlineSnapshot(` + "# TypeScript Graph + + \`\`\`bash + tsg --tsconfig ./dummy_project/tsconfig.json --include includeFiles config --exclude excludeFiles utils --abstraction abstractions --md __tmp__/test-vue.md --vue + \`\`\` + + \`\`\`mermaid + flowchart + classDef dir fill:#0000,stroke:#999 + subgraph src["src"] + src/config.ts["config.ts"] + src/main.ts["main.ts"] + subgraph src/includeFiles["/includeFiles"] + src/includeFiles/b.ts["b.ts"] + src/includeFiles/c.ts["c.ts"] + src/includeFiles/a.ts["a.ts"] + src/includeFiles/abstractions["/abstractions"]:::dir + src/includeFiles/D.vue["D.vue"] + subgraph src/includeFiles/children["/children"] + src/includeFiles/children/childA.ts["childA.ts"] + end + subgraph src/includeFiles/d["/d"] + src/includeFiles/d/d.ts["d.ts"] + src/includeFiles/d/index.ts["index.ts"] + end + subgraph src/includeFiles/components["/components"] + src/includeFiles/components/HelloWorld.vue["HelloWorld.vue"] + end + end + subgraph src/otherFiles["/otherFiles"] + src/otherFiles/e.ts["e.ts"] + end + end + src/includeFiles/b.ts-->src/config.ts + src/includeFiles/c.ts-->src/includeFiles/b.ts + src/config.ts-->src/includeFiles/c.ts + src/includeFiles/d/index.ts-->src/includeFiles/d/d.ts + src/includeFiles/a.ts-->src/includeFiles/children/childA.ts + src/includeFiles/a.ts-->src/includeFiles/d/index.ts + src/otherFiles/e.ts-->src/config.ts + src/includeFiles/abstractions-->data.json + src/includeFiles/D.vue-->src/includeFiles/components/HelloWorld.vue + src/main.ts-->src/includeFiles/a.ts + src/main.ts-->src/includeFiles/b.ts + src/main.ts-->src/includeFiles/abstractions + src/main.ts-->src/includeFiles/D.vue + src/main.ts-->src/otherFiles/e.ts + \`\`\` + " + `); +}); + +test('run:sample:highlight', async () => { + await $`ts-node -O '{\"module\": \"commonjs\"}' ./src/index.ts --tsconfig './dummy_project/tsconfig.json' --include includeFiles config --exclude excludeFiles utils --abstraction abstractions --highlight config.ts b.ts --md ${filepath} --vue`; + + const file = fs.readFileSync(filepath, { encoding: 'utf-8' }); + expect(file).toMatchInlineSnapshot(` + "# TypeScript Graph + + \`\`\`bash + tsg --tsconfig ./dummy_project/tsconfig.json --include includeFiles config --exclude excludeFiles utils --abstraction abstractions --highlight config.ts b.ts --md __tmp__/test-vue.md --vue + \`\`\` + + \`\`\`mermaid + flowchart + classDef dir fill:#0000,stroke:#999 + classDef highlight fill:yellow,color:black + subgraph src["src"] + src/config.ts["config.ts"]:::highlight + src/main.ts["main.ts"] + subgraph src/includeFiles["/includeFiles"] + src/includeFiles/b.ts["b.ts"]:::highlight + src/includeFiles/c.ts["c.ts"] + src/includeFiles/a.ts["a.ts"] + src/includeFiles/abstractions["/abstractions"]:::dir + src/includeFiles/D.vue["D.vue"] + subgraph src/includeFiles/children["/children"] + src/includeFiles/children/childA.ts["childA.ts"] + end + subgraph src/includeFiles/d["/d"] + src/includeFiles/d/d.ts["d.ts"] + src/includeFiles/d/index.ts["index.ts"] + end + subgraph src/includeFiles/components["/components"] + src/includeFiles/components/HelloWorld.vue["HelloWorld.vue"] + end + end + subgraph src/otherFiles["/otherFiles"] + src/otherFiles/e.ts["e.ts"] + end + end + src/includeFiles/b.ts-->src/config.ts + src/includeFiles/c.ts-->src/includeFiles/b.ts + src/config.ts-->src/includeFiles/c.ts + src/includeFiles/d/index.ts-->src/includeFiles/d/d.ts + src/includeFiles/a.ts-->src/includeFiles/children/childA.ts + src/includeFiles/a.ts-->src/includeFiles/d/index.ts + src/otherFiles/e.ts-->src/config.ts + src/includeFiles/abstractions-->data.json + src/includeFiles/D.vue-->src/includeFiles/components/HelloWorld.vue + src/main.ts-->src/includeFiles/a.ts + src/main.ts-->src/includeFiles/b.ts + src/main.ts-->src/includeFiles/abstractions + src/main.ts-->src/includeFiles/D.vue + src/main.ts-->src/otherFiles/e.ts + \`\`\` + " + `); +}); diff --git a/package-lock.json b/package-lock.json index bde08ce2..c5bcec55 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,7 +10,7 @@ "license": "ISC", "dependencies": { "commander": "12.1.0", - "remeda": "2.17.3", + "remeda": "2.17.4", "zod": "3.23.8" }, "bin": { @@ -18,20 +18,20 @@ }, "devDependencies": { "@total-typescript/ts-reset": "0.6.1", - "@types/node": "22.9.1", - "@typescript-eslint/eslint-plugin": "8.14.0", - "@typescript-eslint/parser": "8.14.0", + "@types/node": "22.10.1", + "@typescript-eslint/eslint-plugin": "8.16.0", + "@typescript-eslint/parser": "8.16.0", "commitizen": "4.3.1", "cz-conventional-changelog": "3.3.0", - "eslint": "9.14.0", + "eslint": "9.15.0", "eslint-config-prettier": "9.1.0", "husky": "9.1.7", "lint-staged": "15.2.10", - "prettier": "3.3.3", + "prettier": "3.4.1", "semantic-release": "24.2.0", "ts-node": "10.9.2", - "typescript": "5.6.3", - "vitest": "2.1.5", + "typescript": "5.7.2", + "vitest": "2.1.6", "zx": "8.2.2" } }, @@ -769,9 +769,9 @@ } }, "node_modules/@eslint/config-array": { - "version": "0.18.0", - "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.18.0.tgz", - "integrity": "sha512-fTxvnS1sRMu3+JjXwJG0j/i4RT9u4qJ+lqS/yCGap4lH4zZGzQ7tu+xZqQmcMZq5OBZDL4QRxQzRjkWcGt8IVw==", + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.19.0.tgz", + "integrity": "sha512-zdHg2FPIFNKPdcHWtiNT+jEFCHYVplAXRDlQDyqy0zGx/q2parwh7brGJSiTxRk/TSMkbM//zt/f5CHgyTyaSQ==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -784,9 +784,9 @@ } }, "node_modules/@eslint/core": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.7.0.tgz", - "integrity": "sha512-xp5Jirz5DyPYlPiKat8jaq0EmYvDXKKpzTbxXMpT9eqlRJkRKIz9AGMdlvYjih+im+QlhWrpvVjl8IPC/lHlUw==", + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.9.0.tgz", + "integrity": "sha512-7ATR9F0e4W85D/0w7cU0SNj7qkAexMG+bAHEZOjo9akvGuhHE2m7umzWzfnpa0XAg5Kxc1BWmtPMV67jJ+9VUg==", "dev": true, "license": "Apache-2.0", "engines": { @@ -794,9 +794,9 @@ } }, "node_modules/@eslint/eslintrc": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.1.0.tgz", - "integrity": "sha512-4Bfj15dVJdoy3RfZmmo86RK1Fwzn6SstsvK9JS+BaVKqC6QQQQyXekNaC+g+LKNgkQ+2VhGAzm6hO40AhMR3zQ==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.2.0.tgz", + "integrity": "sha512-grOjVNN8P3hjJn/eIETF1wwd12DdnwFDoyceUJLYYdkpbwq3nLi+4fqrTAONx7XDALqlL220wC/RHSC/QTI/0w==", "dev": true, "license": "MIT", "dependencies": { @@ -818,9 +818,9 @@ } }, "node_modules/@eslint/js": { - "version": "9.14.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.14.0.tgz", - "integrity": "sha512-pFoEtFWCPyDOl+C6Ift+wC7Ro89otjigCf5vcuWqWgqNSQbRrpjSvdeE6ofLz4dHmyxD5f7gIdGT4+p36L6Twg==", + "version": "9.15.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.15.0.tgz", + "integrity": "sha512-tMTqrY+EzbXmKJR5ToI8lxu7jaN5EdmrBFJpQk5JmSlyLsx6o4t27r883K5xsLuCYCpfKBCGswMSWXsM+jB7lg==", "dev": true, "license": "MIT", "engines": { @@ -838,9 +838,9 @@ } }, "node_modules/@eslint/plugin-kit": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.2.0.tgz", - "integrity": "sha512-vH9PiIMMwvhCx31Af3HiGzsVNULDbyVkHXwlemn/B0TFj/00ho3y55efXrUZTfQipxoHC5u4xq6zblww1zm1Ig==", + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.2.3.tgz", + "integrity": "sha512-2b/g5hRmpbb1o4GnTZax9N9m0FXzz9OV42ZzI4rDDMDuHUqigAiQCEWChBWCY4ztAGVRjoWT19v0yMmc5/L5kA==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -1665,13 +1665,13 @@ } }, "node_modules/@types/node": { - "version": "22.9.1", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.9.1.tgz", - "integrity": "sha512-p8Yy/8sw1caA8CdRIQBG5tiLHmxtQKObCijiAa9Ez+d4+PRffM4054xbju0msf+cvhJpnFEeNjxmVT/0ipktrg==", + "version": "22.10.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.10.1.tgz", + "integrity": "sha512-qKgsUwfHZV2WCWLAnVP1JqnpE6Im6h3Y0+fYgMTasNQ7V++CBX5OT1as0g0f+OyubbFqhf6XVNIsmN4IIhEgGQ==", "dev": true, "license": "MIT", "dependencies": { - "undici-types": "~6.19.8" + "undici-types": "~6.20.0" } }, "node_modules/@types/normalize-package-data": { @@ -1688,17 +1688,17 @@ "license": "MIT" }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.14.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.14.0.tgz", - "integrity": "sha512-tqp8H7UWFaZj0yNO6bycd5YjMwxa6wIHOLZvWPkidwbgLCsBMetQoGj7DPuAlWa2yGO3H48xmPwjhsSPPCGU5w==", + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.16.0.tgz", + "integrity": "sha512-5YTHKV8MYlyMI6BaEG7crQ9BhSc8RxzshOReKwZwRWN0+XvvTOm+L/UYLCYxFpfwYuAAqhxiq4yae0CMFwbL7Q==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/regexpp": "^4.10.0", - "@typescript-eslint/scope-manager": "8.14.0", - "@typescript-eslint/type-utils": "8.14.0", - "@typescript-eslint/utils": "8.14.0", - "@typescript-eslint/visitor-keys": "8.14.0", + "@typescript-eslint/scope-manager": "8.16.0", + "@typescript-eslint/type-utils": "8.16.0", + "@typescript-eslint/utils": "8.16.0", + "@typescript-eslint/visitor-keys": "8.16.0", "graphemer": "^1.4.0", "ignore": "^5.3.1", "natural-compare": "^1.4.0", @@ -1722,16 +1722,16 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "8.14.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.14.0.tgz", - "integrity": "sha512-2p82Yn9juUJq0XynBXtFCyrBDb6/dJombnz6vbo6mgQEtWHfvHbQuEa9kAOVIt1c9YFwi7H6WxtPj1kg+80+RA==", + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.16.0.tgz", + "integrity": "sha512-D7DbgGFtsqIPIFMPJwCad9Gfi/hC0PWErRRHFnaCWoEDYi5tQUDiJCTmGUbBiLzjqAck4KcXt9Ayj0CNlIrF+w==", "dev": true, "license": "BSD-2-Clause", "dependencies": { - "@typescript-eslint/scope-manager": "8.14.0", - "@typescript-eslint/types": "8.14.0", - "@typescript-eslint/typescript-estree": "8.14.0", - "@typescript-eslint/visitor-keys": "8.14.0", + "@typescript-eslint/scope-manager": "8.16.0", + "@typescript-eslint/types": "8.16.0", + "@typescript-eslint/typescript-estree": "8.16.0", + "@typescript-eslint/visitor-keys": "8.16.0", "debug": "^4.3.4" }, "engines": { @@ -1751,14 +1751,14 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "8.14.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.14.0.tgz", - "integrity": "sha512-aBbBrnW9ARIDn92Zbo7rguLnqQ/pOrUguVpbUwzOhkFg2npFDwTgPGqFqE0H5feXcOoJOfX3SxlJaKEVtq54dw==", + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.16.0.tgz", + "integrity": "sha512-mwsZWubQvBki2t5565uxF0EYvG+FwdFb8bMtDuGQLdCCnGPrDEDvm1gtfynuKlnpzeBRqdFCkMf9jg1fnAK8sg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.14.0", - "@typescript-eslint/visitor-keys": "8.14.0" + "@typescript-eslint/types": "8.16.0", + "@typescript-eslint/visitor-keys": "8.16.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1769,14 +1769,14 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "8.14.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.14.0.tgz", - "integrity": "sha512-Xcz9qOtZuGusVOH5Uk07NGs39wrKkf3AxlkK79RBK6aJC1l03CobXjJbwBPSidetAOV+5rEVuiT1VSBUOAsanQ==", + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.16.0.tgz", + "integrity": "sha512-IqZHGG+g1XCWX9NyqnI/0CX5LL8/18awQqmkZSl2ynn8F76j579dByc0jhfVSnSnhf7zv76mKBQv9HQFKvDCgg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/typescript-estree": "8.14.0", - "@typescript-eslint/utils": "8.14.0", + "@typescript-eslint/typescript-estree": "8.16.0", + "@typescript-eslint/utils": "8.16.0", "debug": "^4.3.4", "ts-api-utils": "^1.3.0" }, @@ -1787,6 +1787,9 @@ "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0" + }, "peerDependenciesMeta": { "typescript": { "optional": true @@ -1794,9 +1797,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "8.14.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.14.0.tgz", - "integrity": "sha512-yjeB9fnO/opvLJFAsPNYlKPnEM8+z4og09Pk504dkqonT02AyL5Z9SSqlE0XqezS93v6CXn49VHvB2G7XSsl0g==", + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.16.0.tgz", + "integrity": "sha512-NzrHj6thBAOSE4d9bsuRNMvk+BvaQvmY4dDglgkgGC0EW/tB3Kelnp3tAKH87GEwzoxgeQn9fNGRyFJM/xd+GQ==", "dev": true, "license": "MIT", "engines": { @@ -1808,14 +1811,14 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.14.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.14.0.tgz", - "integrity": "sha512-OPXPLYKGZi9XS/49rdaCbR5j/S14HazviBlUQFvSKz3npr3NikF+mrgK7CFVur6XEt95DZp/cmke9d5i3vtVnQ==", + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.16.0.tgz", + "integrity": "sha512-E2+9IzzXMc1iaBy9zmo+UYvluE3TW7bCGWSF41hVWUE01o8nzr1rvOQYSxelxr6StUvRcTMe633eY8mXASMaNw==", "dev": true, "license": "BSD-2-Clause", "dependencies": { - "@typescript-eslint/types": "8.14.0", - "@typescript-eslint/visitor-keys": "8.14.0", + "@typescript-eslint/types": "8.16.0", + "@typescript-eslint/visitor-keys": "8.16.0", "debug": "^4.3.4", "fast-glob": "^3.3.2", "is-glob": "^4.0.3", @@ -1863,16 +1866,16 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "8.14.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.14.0.tgz", - "integrity": "sha512-OGqj6uB8THhrHj0Fk27DcHPojW7zKwKkPmHXHvQ58pLYp4hy8CSUdTKykKeh+5vFqTTVmjz0zCOOPKRovdsgHA==", + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.16.0.tgz", + "integrity": "sha512-C1zRy/mOL8Pj157GiX4kaw7iyRLKfJXBR3L82hk5kS/GyHcOFmy4YUq/zfZti72I9wnuQtA/+xzft4wCC8PJdA==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", - "@typescript-eslint/scope-manager": "8.14.0", - "@typescript-eslint/types": "8.14.0", - "@typescript-eslint/typescript-estree": "8.14.0" + "@typescript-eslint/scope-manager": "8.16.0", + "@typescript-eslint/types": "8.16.0", + "@typescript-eslint/typescript-estree": "8.16.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1883,17 +1886,22 @@ }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.14.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.14.0.tgz", - "integrity": "sha512-vG0XZo8AdTH9OE6VFRwAZldNc7qtJ/6NLGWak+BtENuEUXGZgFpihILPiBvKXvJ2nFu27XNGC6rKiwuaoMbYzQ==", + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.16.0.tgz", + "integrity": "sha512-pq19gbaMOmFE3CbL0ZB8J8BFCo2ckfHBfaIsaOZgBIF4EoISJIdLX5xRhd0FGB0LlHReNRuzoJoMGpTjq8F2CQ==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.14.0", - "eslint-visitor-keys": "^3.4.3" + "@typescript-eslint/types": "8.16.0", + "eslint-visitor-keys": "^4.2.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1903,15 +1911,28 @@ "url": "https://opencollective.com/typescript-eslint" } }, + "node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz", + "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, "node_modules/@vitest/expect": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-2.1.5.tgz", - "integrity": "sha512-nZSBTW1XIdpZvEJyoP/Sy8fUg0b8od7ZpGDkTUcfJ7wz/VoZAFzFfLyxVxGFhUjJzhYqSbIpfMtl/+k/dpWa3Q==", + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-2.1.6.tgz", + "integrity": "sha512-9M1UR9CAmrhJOMoSwVnPh2rELPKhYo0m/CSgqw9PyStpxtkwhmdM6XYlXGKeYyERY1N6EIuzkQ7e3Lm1WKCoUg==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/spy": "2.1.5", - "@vitest/utils": "2.1.5", + "@vitest/spy": "2.1.6", + "@vitest/utils": "2.1.6", "chai": "^5.1.2", "tinyrainbow": "^1.2.0" }, @@ -1920,13 +1941,13 @@ } }, "node_modules/@vitest/mocker": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-2.1.5.tgz", - "integrity": "sha512-XYW6l3UuBmitWqSUXTNXcVBUCRytDogBsWuNXQijc00dtnU/9OqpXWp4OJroVrad/gLIomAq9aW8yWDBtMthhQ==", + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-2.1.6.tgz", + "integrity": "sha512-MHZp2Z+Q/A3am5oD4WSH04f9B0T7UvwEb+v5W0kCYMhtXGYbdyl2NUk1wdSMqGthmhpiThPDp/hEoVwu16+u1A==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/spy": "2.1.5", + "@vitest/spy": "2.1.6", "estree-walker": "^3.0.3", "magic-string": "^0.30.12" }, @@ -1935,7 +1956,7 @@ }, "peerDependencies": { "msw": "^2.4.9", - "vite": "^5.0.0" + "vite": "^5.0.0 || ^6.0.0" }, "peerDependenciesMeta": { "msw": { @@ -1947,9 +1968,9 @@ } }, "node_modules/@vitest/pretty-format": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-2.1.5.tgz", - "integrity": "sha512-4ZOwtk2bqG5Y6xRGHcveZVr+6txkH7M2e+nPFd6guSoN638v/1XQ0K06eOpi0ptVU/2tW/pIU4IoPotY/GZ9fw==", + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-2.1.6.tgz", + "integrity": "sha512-exZyLcEnHgDMKc54TtHca4McV4sKT+NKAe9ix/yhd/qkYb/TP8HTyXRFDijV19qKqTZM0hPL4753zU/U8L/gAA==", "dev": true, "license": "MIT", "dependencies": { @@ -1960,13 +1981,13 @@ } }, "node_modules/@vitest/runner": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-2.1.5.tgz", - "integrity": "sha512-pKHKy3uaUdh7X6p1pxOkgkVAFW7r2I818vHDthYLvUyjRfkKOU6P45PztOch4DZarWQne+VOaIMwA/erSSpB9g==", + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-2.1.6.tgz", + "integrity": "sha512-SjkRGSFyrA82m5nz7To4CkRSEVWn/rwQISHoia/DB8c6IHIhaE/UNAo+7UfeaeJRE979XceGl00LNkIz09RFsA==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/utils": "2.1.5", + "@vitest/utils": "2.1.6", "pathe": "^1.1.2" }, "funding": { @@ -1974,13 +1995,13 @@ } }, "node_modules/@vitest/snapshot": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-2.1.5.tgz", - "integrity": "sha512-zmYw47mhfdfnYbuhkQvkkzYroXUumrwWDGlMjpdUr4jBd3HZiV2w7CQHj+z7AAS4VOtWxI4Zt4bWt4/sKcoIjg==", + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-2.1.6.tgz", + "integrity": "sha512-5JTWHw8iS9l3v4/VSuthCndw1lN/hpPB+mlgn1BUhFbobeIUj1J1V/Bj2t2ovGEmkXLTckFjQddsxS5T6LuVWw==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/pretty-format": "2.1.5", + "@vitest/pretty-format": "2.1.6", "magic-string": "^0.30.12", "pathe": "^1.1.2" }, @@ -1989,9 +2010,9 @@ } }, "node_modules/@vitest/spy": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-2.1.5.tgz", - "integrity": "sha512-aWZF3P0r3w6DiYTVskOYuhBc7EMc3jvn1TkBg8ttylFFRqNN2XGD7V5a4aQdk6QiUzZQ4klNBSpCLJgWNdIiNw==", + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-2.1.6.tgz", + "integrity": "sha512-oTFObV8bd4SDdRka5O+mSh5w9irgx5IetrD5i+OsUUsk/shsBoHifwCzy45SAORzAhtNiprUVaK3hSCCzZh1jQ==", "dev": true, "license": "MIT", "dependencies": { @@ -2002,13 +2023,13 @@ } }, "node_modules/@vitest/utils": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-2.1.5.tgz", - "integrity": "sha512-yfj6Yrp0Vesw2cwJbP+cl04OC+IHFsuQsrsJBL9pyGeQXE56v1UAOQco+SR55Vf1nQzfV0QJg1Qum7AaWUwwYg==", + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-2.1.6.tgz", + "integrity": "sha512-ixNkFy3k4vokOUTU2blIUvOgKq/N2PW8vKIjZZYsGJCMX69MRa9J2sKqX5hY/k5O5Gty3YJChepkqZ3KM9LyIQ==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/pretty-format": "2.1.5", + "@vitest/pretty-format": "2.1.6", "loupe": "^3.1.2", "tinyrainbow": "^1.2.0" }, @@ -2924,10 +2945,11 @@ "dev": true }, "node_modules/cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", "dev": true, + "license": "MIT", "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", @@ -3484,27 +3506,27 @@ } }, "node_modules/eslint": { - "version": "9.14.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.14.0.tgz", - "integrity": "sha512-c2FHsVBr87lnUtjP4Yhvk4yEhKrQavGafRA/Se1ouse8PfbfC/Qh9Mxa00yWsZRlqeUB9raXip0aiiUZkgnr9g==", + "version": "9.15.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.15.0.tgz", + "integrity": "sha512-7CrWySmIibCgT1Os28lUU6upBshZ+GxybLOrmRzi08kS8MBuO8QA7pXEgYgY5W8vK3e74xv0lpjo9DbaGU9Rkw==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.12.1", - "@eslint/config-array": "^0.18.0", - "@eslint/core": "^0.7.0", - "@eslint/eslintrc": "^3.1.0", - "@eslint/js": "9.14.0", - "@eslint/plugin-kit": "^0.2.0", + "@eslint/config-array": "^0.19.0", + "@eslint/core": "^0.9.0", + "@eslint/eslintrc": "^3.2.0", + "@eslint/js": "9.15.0", + "@eslint/plugin-kit": "^0.2.3", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", - "@humanwhocodes/retry": "^0.4.0", + "@humanwhocodes/retry": "^0.4.1", "@types/estree": "^1.0.6", "@types/json-schema": "^7.0.15", "ajv": "^6.12.4", "chalk": "^4.0.0", - "cross-spawn": "^7.0.2", + "cross-spawn": "^7.0.5", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", "eslint-scope": "^8.2.0", @@ -3523,8 +3545,7 @@ "lodash.merge": "^4.6.2", "minimatch": "^3.1.2", "natural-compare": "^1.4.0", - "optionator": "^0.9.3", - "text-table": "^0.2.0" + "optionator": "^0.9.3" }, "bin": { "eslint": "bin/eslint.js" @@ -9271,9 +9292,9 @@ } }, "node_modules/prettier": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.3.3.tgz", - "integrity": "sha512-i2tDNA0O5IrMO757lfrdQZCc2jPNDVntV0m/+4whiDfWaTKfMNgR7Qz0NAeGz/nRqF4m5/6CLzbP4/liHt12Ew==", + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.4.1.tgz", + "integrity": "sha512-G+YdqtITVZmOJje6QkXQWzl3fSfMxFwm1tjTyo9exhkmWSqC4Yhd1+lug++IlR2mvRVAxEDDWYkQdeSztajqgg==", "dev": true, "license": "MIT", "bin": { @@ -9465,9 +9486,9 @@ } }, "node_modules/remeda": { - "version": "2.17.3", - "resolved": "https://registry.npmjs.org/remeda/-/remeda-2.17.3.tgz", - "integrity": "sha512-xyi2rCQkz2j4BEWbWxPw6JCapv1yBuSwr4Uf9BX00AkesAJaiKvc6Il6thsBidwVZAtNiSaCIXvslkKL0ybz8w==", + "version": "2.17.4", + "resolved": "https://registry.npmjs.org/remeda/-/remeda-2.17.4.tgz", + "integrity": "sha512-pviU2Ag7Qx9mOCAKO4voxDx/scfLzdhp3v85qDO4xxntQsU76uE9sgrAAdK1ATn4zzaOJqCXYMMNRP+O9F4Wiw==", "license": "MIT", "dependencies": { "type-fest": "^4.27.0" @@ -10527,12 +10548,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/text-table": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", - "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", - "dev": true - }, "node_modules/thenify": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", @@ -10785,9 +10800,9 @@ } }, "node_modules/typescript": { - "version": "5.6.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.3.tgz", - "integrity": "sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw==", + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.7.2.tgz", + "integrity": "sha512-i5t66RHxDvVN40HfDd1PsEThGNnlMCMT3jMUuoh9/0TaqWevNontacunWyN02LA9/fIbEWlcHZcgTKb9QoaLfg==", "dev": true, "license": "Apache-2.0", "bin": { @@ -10813,9 +10828,9 @@ } }, "node_modules/undici-types": { - "version": "6.19.8", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", - "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==", + "version": "6.20.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz", + "integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==", "dev": true, "license": "MIT" }, @@ -10994,9 +11009,9 @@ } }, "node_modules/vite-node": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-2.1.5.tgz", - "integrity": "sha512-rd0QIgx74q4S1Rd56XIiL2cYEdyWn13cunYBIuqh9mpmQr7gGS0IxXoP8R6OaZtNQQLyXSWbd4rXKYUbhFpK5w==", + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-2.1.6.tgz", + "integrity": "sha512-DBfJY0n9JUwnyLxPSSUmEePT21j8JZp/sR9n+/gBwQU6DcQOioPdb8/pibWfXForbirSagZCilseYIwaL3f95A==", "dev": true, "license": "MIT", "dependencies": { @@ -11004,32 +11019,32 @@ "debug": "^4.3.7", "es-module-lexer": "^1.5.4", "pathe": "^1.1.2", - "vite": "^5.0.0" + "vite": "^5.0.0 || ^6.0.0" }, "bin": { "vite-node": "vite-node.mjs" }, "engines": { - "node": "^18.0.0 || >=20.0.0" + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" }, "funding": { "url": "https://opencollective.com/vitest" } }, "node_modules/vitest": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/vitest/-/vitest-2.1.5.tgz", - "integrity": "sha512-P4ljsdpuzRTPI/kbND2sDZ4VmieerR2c9szEZpjc+98Z9ebvnXmM5+0tHEKqYZumXqlvnmfWsjeFOjXVriDG7A==", + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-2.1.6.tgz", + "integrity": "sha512-isUCkvPL30J4c5O5hgONeFRsDmlw6kzFEdLQHLezmDdKQHy8Ke/B/dgdTMEgU0vm+iZ0TjW8GuK83DiahBoKWQ==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/expect": "2.1.5", - "@vitest/mocker": "2.1.5", - "@vitest/pretty-format": "^2.1.5", - "@vitest/runner": "2.1.5", - "@vitest/snapshot": "2.1.5", - "@vitest/spy": "2.1.5", - "@vitest/utils": "2.1.5", + "@vitest/expect": "2.1.6", + "@vitest/mocker": "2.1.6", + "@vitest/pretty-format": "^2.1.6", + "@vitest/runner": "2.1.6", + "@vitest/snapshot": "2.1.6", + "@vitest/spy": "2.1.6", + "@vitest/utils": "2.1.6", "chai": "^5.1.2", "debug": "^4.3.7", "expect-type": "^1.1.0", @@ -11040,24 +11055,24 @@ "tinyexec": "^0.3.1", "tinypool": "^1.0.1", "tinyrainbow": "^1.2.0", - "vite": "^5.0.0", - "vite-node": "2.1.5", + "vite": "^5.0.0 || ^6.0.0", + "vite-node": "2.1.6", "why-is-node-running": "^2.3.0" }, "bin": { "vitest": "vitest.mjs" }, "engines": { - "node": "^18.0.0 || >=20.0.0" + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" }, "funding": { "url": "https://opencollective.com/vitest" }, "peerDependencies": { "@edge-runtime/vm": "*", - "@types/node": "^18.0.0 || >=20.0.0", - "@vitest/browser": "2.1.5", - "@vitest/ui": "2.1.5", + "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", + "@vitest/browser": "2.1.6", + "@vitest/ui": "2.1.6", "happy-dom": "*", "jsdom": "*" }, diff --git a/package.json b/package.json index da72809c..9d5fdb91 100644 --- a/package.json +++ b/package.json @@ -49,31 +49,31 @@ "prepublishOnly": "npm run build", "prepare": "husky install", "semantic-release": "semantic-release", - "run:tsc:demo": "ts-node ./typescript-compiler-api-demo.ts" + "run:tsc:demo": "ts-node ./src/typescript-compiler-api-demo.ts" }, "author": "ysk8hori", "license": "ISC", "dependencies": { "commander": "12.1.0", - "remeda": "2.17.3", + "remeda": "2.17.4", "zod": "3.23.8" }, "devDependencies": { "@total-typescript/ts-reset": "0.6.1", - "@types/node": "22.9.1", - "@typescript-eslint/eslint-plugin": "8.14.0", - "@typescript-eslint/parser": "8.14.0", + "@types/node": "22.10.1", + "@typescript-eslint/eslint-plugin": "8.16.0", + "@typescript-eslint/parser": "8.16.0", "commitizen": "4.3.1", "cz-conventional-changelog": "3.3.0", - "eslint": "9.14.0", + "eslint": "9.15.0", "eslint-config-prettier": "9.1.0", "husky": "9.1.7", "lint-staged": "15.2.10", - "prettier": "3.3.3", + "prettier": "3.4.1", "semantic-release": "24.2.0", "ts-node": "10.9.2", - "typescript": "5.6.3", - "vitest": "2.1.5", + "typescript": "5.7.2", + "vitest": "2.1.6", "zx": "8.2.2" }, "config": { diff --git a/src/graph/createGraph.ts b/src/graph/createGraph.ts index 68b69ae9..1c86f729 100644 --- a/src/graph/createGraph.ts +++ b/src/graph/createGraph.ts @@ -1,7 +1,10 @@ import path from 'path'; +import fs from 'fs'; +import { tmpdir } from 'os'; import * as ts from 'typescript'; import { Graph, Meta, Node, OptionValues, Relation } from '../models'; import { pipe, piped } from 'remeda'; +import { mergeGraph } from './utils'; export function createGraph( /** @@ -10,7 +13,7 @@ export function createGraph( * include をによる絞り込みを行わない理由は、include から参照される include 指定されていないファイルをここで除外したくないため。 * exclude は、ユーザーが明確に不要と指定しているため、たとえ include に含まれたり include 対象ファイルと関連をもつファイルであったとしても除外して良い。 **/ - opt: Pick, + opt: Pick, ): { graph: Graph; meta: Meta } { const configPath = opt.tsconfig ? path.resolve(opt.tsconfig) @@ -23,97 +26,109 @@ export function createGraph( const rootDir = splitedConfigPath .slice(0, splitedConfigPath.length - 1) .join('/'); - const { options, fileNames } = ts.parseJsonConfigFileContent( - config, - ts.sys, - rootDir, - ); - options.rootDir = rootDir; - const program = ts.createProgram(fileNames, options); - const nodes: Node[] = []; - const relations: Relation[] = []; + const bindWords_isFileNameMatchSomeWords = (array: string[]) => (filename: string) => array.some(word => filename.includes(word)); - const isMatchSomeExclude = opt.exclude ? bindWords_isFileNameMatchSomeWords(opt.exclude) : () => false; const isNotMatchSomeExclude = (filename: string) => !isMatchSomeExclude(filename); - function getFilePath(sourceFile: ts.SourceFile) { - return options.rootDir - ? sourceFile.fileName.replace(options.rootDir + '/', '') - : sourceFile.fileName; + + if (!opt.vue) { + const { options, fileNames: fullFilePaths } = ts.parseJsonConfigFileContent( + config, + ts.sys, + rootDir, + ); + options.rootDir = rootDir; + const program = ts.createProgram(fullFilePaths, options); + const graphs = program + .getSourceFiles() + .filter(sourceFile => !sourceFile.fileName.includes('node_modules')) // node_modules 配下のファイルは除外 + .filter(piped(getFilePath(options), removeSlash, isNotMatchSomeExclude)) + .map(analyzeSoucreFile(options)); + return { graph: mergeGraph(...graphs), meta: { rootDir } }; + } else { + const graph = createGraphForVue(rootDir, config, isNotMatchSomeExclude); + return { graph, meta: { rootDir } }; } +} + +function createGraphForVue( + rootDir: string, + config: any, + isNotMatchSomeExclude: (filename: string) => boolean, +) { + const relativeRootDir = pipe(path.relative(process.cwd(), rootDir), str => + str === '' ? './' : str, + ); - program + // vue と TS ファイルのパスを保持する。その際、すでに *.vue.ts ファイルが存在している場合は対象外とする。 + const vueAndTsFilePaths = getVueAndTsFilePathsRecursive( + relativeRootDir, + ).filter(path => !fs.existsSync(`${path}.ts`)); + + const tmpDir = fs.mkdtempSync(path.join(tmpdir(), 'tsg-vue-')); + console.log('tmpDir:', tmpDir); + vueAndTsFilePaths + .map(fullPath => path.relative(process.cwd(), fullPath)) + .forEach(relativeFilePath => { + const tmpFilePath = path.join( + tmpDir, + // *.vue のファイルは *.vue.ts としてコピー + relativeFilePath.endsWith('.vue') + ? relativeFilePath + '.ts' + : relativeFilePath, + ); + if (!tmpFilePath.startsWith(tmpDir)) { + // tmpDir 以外へのコピーを抑止する + return; + } + fs.mkdirSync(path.dirname(tmpFilePath), { recursive: true }); + fs.copyFileSync(relativeFilePath, tmpFilePath); + }); + + const { options, fileNames: fullFilePaths } = ts.parseJsonConfigFileContent( + config, + ts.sys, + path.join(tmpDir, relativeRootDir), + ); + // rootDir を設定しない場合、 tmpDir/rootDir である場合に `rootDir/` が node についてしまう + options.rootDir = path.join(tmpDir, relativeRootDir); + // ↑ここまでで、ファイルを tmp にコピーし、新たな fileNames と options を生成する + const program = ts.createProgram(fullFilePaths, options); + function renameNode(node: Node) { + return { + ...node, + path: node.path + .replace('.vue.ts', '.vue') + .replace(`${tmpDir.slice(1)}/`, ''), + name: node.name + .replace('.vue.ts', '.vue') + .replace(`${tmpDir.slice(1)}/`, ''), + }; + } + const graphs = program .getSourceFiles() - .filter(sourceFile => !sourceFile.fileName.includes('node_modules')) - .filter(piped(getFilePath, removeSlash, isNotMatchSomeExclude)) - .forEach(sourceFile => { - const filePath = pipe(sourceFile, getFilePath, removeSlash); - const fileName = getName(filePath); - const fromNode: Node = { - path: filePath, - name: fileName, - changeStatus: 'not_modified', - }; - nodes.push(fromNode); - - ts.forEachChild(sourceFile, node => { - const importPaths: (string | undefined)[] = []; - function getModuleNameText(node: ts.Node) { - if (ts.isImportDeclaration(node)) { - importPaths.push(node.moduleSpecifier?.getText(sourceFile)); - } else if (ts.isCallExpression(node)) { - const text = node.getText(sourceFile); - if (text.includes('require') || text.includes('import')) { - importPaths.push(node.arguments[0]?.getText(sourceFile)); - } - } else if (ts.isExportDeclaration(node)) { - importPaths.push(node.moduleSpecifier?.getText(sourceFile)); - } - ts.forEachChild(node, getModuleNameText); - } - getModuleNameText(node); - - importPaths.forEach(moduleNameText => { - if (!moduleNameText) return; - const moduleName = moduleNameText.slice(1, moduleNameText.length - 1); // import 文のクォート及びダブルクォートを除去 - const moduleFileFullName = - ts.resolveModuleName( - moduleName, - sourceFile.fileName, - options, - ts.sys, - ).resolvedModule?.resolvedFileName ?? ''; - const moduleFilePath = removeSlash( - options.rootDir - ? moduleFileFullName.replace(options.rootDir, '') - : moduleFileFullName, - ); - if (!moduleFilePath) return; - const toNode: Node = { - path: moduleFilePath, - name: getName(moduleFilePath), - changeStatus: 'not_modified', + .filter(sourceFile => !sourceFile.fileName.includes('node_modules')) // node_modules 配下のファイルは除外 + .filter(piped(getFilePath(options), removeSlash, isNotMatchSomeExclude)) + .map(analyzeSoucreFile(options)) + .map(graph => { + // graph においては .vue.ts ファイルを .vue に戻す + return { + nodes: graph.nodes.map(renameNode), + relations: graph.relations.map(relation => { + return { + ...relation, + from: renameNode(relation.from), + to: renameNode(relation.to), }; - if (!findNode(nodes, moduleFilePath)) { - nodes.push(toNode); - } - relations.push({ - kind: 'depends_on', - from: fromNode, - to: toNode, - fullText: node.getChildAt(1, sourceFile)?.getText(sourceFile) ?? '', - changeStatus: 'not_modified', - }); - }); - }); + }), + }; }); - - return { graph: { nodes, relations }, meta: { rootDir } }; + return mergeGraph(...graphs); } function getName(filePath: string) { @@ -141,3 +156,121 @@ function findNode(nodes: Node[], filePath: string): Node | undefined { function removeSlash(pathName: string): string { return pathName.startsWith('/') ? pathName.replace('/', '') : pathName; } + +function getFilePath( + options: ts.CompilerOptions, +): (sourceFile: ts.SourceFile) => string { + return (sourceFile: ts.SourceFile) => + options.rootDir + ? sourceFile.fileName.replace(options.rootDir + '/', '') + : sourceFile.fileName; +} + +function analyzeSoucreFile( + options: ts.CompilerOptions, +): (sourceFile: ts.SourceFile) => Graph { + return (sourceFile: ts.SourceFile) => { + const nodes: Node[] = []; + const relations: Relation[] = []; + const filePath = pipe(sourceFile, getFilePath(options), removeSlash); + const fileName = getName(filePath); + const fromNode: Node = { + path: filePath, + name: fileName, + changeStatus: 'not_modified', + }; + nodes.push(fromNode); + + ts.forEachChild(sourceFile, node => { + const importPaths: (string | undefined)[] = []; + function getModuleNameText(node: ts.Node) { + if (ts.isImportDeclaration(node)) { + importPaths.push(node.moduleSpecifier?.getText(sourceFile)); + } else if (ts.isCallExpression(node)) { + const text = node.getText(sourceFile); + if (text.includes('require') || text.includes('import')) { + importPaths.push(node.arguments[0]?.getText(sourceFile)); + } + } else if (ts.isExportDeclaration(node)) { + importPaths.push(node.moduleSpecifier?.getText(sourceFile)); + } + ts.forEachChild(node, getModuleNameText); + } + getModuleNameText(node); + + importPaths.forEach(moduleNameText => { + if (!moduleNameText) { + return; + } + const moduleName = moduleNameText.slice(1, moduleNameText.length - 1); // import 文のクォート及びダブルクォートを除去 + const moduleFileFullName = + ts.resolveModuleName(moduleName, sourceFile.fileName, options, ts.sys) + .resolvedModule?.resolvedFileName ?? ''; + const moduleFilePath = removeSlash( + options.rootDir + ? moduleFileFullName.replace(options.rootDir, '') + : moduleFileFullName, + ); + if (!moduleFilePath) { + return; + } + const toNode: Node = { + path: moduleFilePath, + name: getName(moduleFilePath), + changeStatus: 'not_modified', + }; + if (!findNode(nodes, moduleFilePath)) { + nodes.push(toNode); + } + relations.push({ + kind: 'depends_on', + from: fromNode, + to: toNode, + fullText: node.getChildAt(1, sourceFile)?.getText(sourceFile) ?? '', + changeStatus: 'not_modified', + }); + }); + }); + return { nodes, relations }; + }; +} + +function getVueAndTsFilePathsRecursive( + dir: string, + mut_filePaths: string[] = [], +): string[] { + const files = fs.readdirSync(dir); + files.forEach(file => { + const filePath = path.join(dir, file); + if ( + fs.statSync(filePath).isDirectory() && + !filePath.includes('node_modules') + ) { + // ディレクトリの場合は再帰的に呼び出す + return getVueAndTsFilePathsRecursive(filePath, mut_filePaths); + } + + if ( + // ts.Extension and vue + [ + '.ts', + '.tsx', + '.d.ts', + '.js', + '.jsx', + '.json', + '.tsbuildinfo', + '.mjs', + '.mts', + '.d.mts', + '.cjs', + '.cts', + '.d.cts', + '.vue', + ].some(ext => filePath.endsWith(ext)) + ) { + mut_filePaths.push(filePath); + } + }); + return mut_filePaths; +} diff --git a/src/index.ts b/src/index.ts index 96e98ba0..fde91c1c 100644 --- a/src/index.ts +++ b/src/index.ts @@ -61,7 +61,8 @@ program .option( '--config-file', 'Specify the relative path to the config file (from cwd or specified by -d, --dir). Default is .tsgrc.json.', - ); + ) + .option('--vue', '(experimental) Enable Vue.js support'); program.parse(); const opt = program.opts>(); diff --git a/src/models.ts b/src/models.ts index e231909b..974c0911 100644 --- a/src/models.ts +++ b/src/models.ts @@ -11,6 +11,7 @@ export type OptionValues = { TB?: boolean; configFile?: string; measureInstability?: boolean; + vue?: boolean; }; type FileName = string;