Skip to content

Commit d39e54f

Browse files
committed
feat(creategraph): Vue を解析可能とする
1 parent efb072c commit d39e54f

File tree

1 file changed

+131
-22
lines changed

1 file changed

+131
-22
lines changed

src/graph/createGraph.ts

Lines changed: 131 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
import path from 'path';
2+
import fs from 'fs';
3+
import { tmpdir } from 'os';
24
import * as ts from 'typescript';
35
import { Graph, Meta, Node, OptionValues, Relation } from '../models';
46
import { pipe, piped } from 'remeda';
@@ -24,13 +26,6 @@ export function createGraph(
2426
const rootDir = splitedConfigPath
2527
.slice(0, splitedConfigPath.length - 1)
2628
.join('/');
27-
const { options, fileNames } = ts.parseJsonConfigFileContent(
28-
config,
29-
ts.sys,
30-
rootDir,
31-
);
32-
options.rootDir = rootDir;
33-
const program = ts.createProgram(fileNames, options);
3429

3530
const bindWords_isFileNameMatchSomeWords =
3631
(array: string[]) => (filename: string) =>
@@ -41,13 +36,99 @@ export function createGraph(
4136
const isNotMatchSomeExclude = (filename: string) =>
4237
!isMatchSomeExclude(filename);
4338

39+
if (false) {
40+
const { options, fileNames: fullFilePaths } = ts.parseJsonConfigFileContent(
41+
config,
42+
ts.sys,
43+
rootDir,
44+
);
45+
options.rootDir = rootDir;
46+
const program = ts.createProgram(fullFilePaths, options);
47+
const graphs = program
48+
.getSourceFiles()
49+
.filter(sourceFile => !sourceFile.fileName.includes('node_modules')) // node_modules 配下のファイルは除外
50+
.filter(piped(getFilePath(options), removeSlash, isNotMatchSomeExclude))
51+
.map(analyzeSoucreFile(options));
52+
return { graph: mergeGraph(...graphs), meta: { rootDir } };
53+
} else {
54+
const graph = createGraphForVue(rootDir, config, isNotMatchSomeExclude);
55+
return { graph, meta: { rootDir } };
56+
}
57+
}
58+
59+
function createGraphForVue(
60+
rootDir: string,
61+
config: any,
62+
isNotMatchSomeExclude: (filename: string) => boolean,
63+
) {
64+
const relativeRootDir = pipe(path.relative(process.cwd(), rootDir), str =>
65+
str === '' ? './' : str,
66+
);
67+
68+
// vue と TS ファイルのパスを保持する。その際、すでに *.vue.ts ファイルが存在している場合は対象外とする。
69+
const vueAndTsFilePaths = getVueAndTsFilePathsRecursive(
70+
relativeRootDir,
71+
).filter(path => !fs.existsSync(`${path}.ts`));
72+
73+
const tmpDir = fs.mkdtempSync(path.join(tmpdir(), 'tsg-vue-'));
74+
console.log('tmpDir:', tmpDir);
75+
vueAndTsFilePaths
76+
.map(fullPath => path.relative(process.cwd(), fullPath))
77+
.forEach(relativeFilePath => {
78+
const tmpFilePath = path.join(
79+
tmpDir,
80+
// *.vue のファイルは *.vue.ts としてコピー
81+
relativeFilePath.endsWith('.vue')
82+
? relativeFilePath + '.ts'
83+
: relativeFilePath,
84+
);
85+
if (!tmpFilePath.startsWith(tmpDir)) {
86+
// tmpDir 以外へのコピーを抑止する
87+
return;
88+
}
89+
fs.mkdirSync(path.dirname(tmpFilePath), { recursive: true });
90+
fs.copyFileSync(relativeFilePath, tmpFilePath);
91+
});
92+
93+
const { options, fileNames: fullFilePaths } = ts.parseJsonConfigFileContent(
94+
config,
95+
ts.sys,
96+
path.join(tmpDir, relativeRootDir),
97+
);
98+
// rootDir を設定しない場合、 tmpDir/rootDir である場合に `rootDir/` が node についてしまう
99+
options.rootDir = path.join(tmpDir, relativeRootDir);
100+
// ↑ここまでで、ファイルを tmp にコピーし、新たな fileNames と options を生成する
101+
const program = ts.createProgram(fullFilePaths, options);
102+
function renameNode(node: Node) {
103+
return {
104+
...node,
105+
path: node.path
106+
.replace('.vue.ts', '.vue')
107+
.replace(`${tmpDir.slice(1)}/`, ''),
108+
name: node.name
109+
.replace('.vue.ts', '.vue')
110+
.replace(`${tmpDir.slice(1)}/`, ''),
111+
};
112+
}
44113
const graphs = program
45114
.getSourceFiles()
46115
.filter(sourceFile => !sourceFile.fileName.includes('node_modules')) // node_modules 配下のファイルは除外
47116
.filter(piped(getFilePath(options), removeSlash, isNotMatchSomeExclude))
48-
.map(analyzeSoucreFile(options));
49-
50-
return { graph: mergeGraph(...graphs), meta: { rootDir } };
117+
.map(analyzeSoucreFile(options))
118+
.map(graph => {
119+
// graph においては .vue.ts ファイルを .vue に戻す
120+
return {
121+
nodes: graph.nodes.map(renameNode),
122+
relations: graph.relations.map(relation => {
123+
return {
124+
...relation,
125+
from: renameNode(relation.from),
126+
to: renameNode(relation.to),
127+
};
128+
}),
129+
};
130+
});
131+
return mergeGraph(...graphs);
51132
}
52133

53134
function getName(filePath: string) {
@@ -104,10 +185,6 @@ function analyzeSoucreFile(
104185
const importPaths: (string | undefined)[] = [];
105186
function getModuleNameText(node: ts.Node) {
106187
if (ts.isImportDeclaration(node)) {
107-
console.log(
108-
'isImportDeclaration',
109-
node.moduleSpecifier?.getText(sourceFile),
110-
);
111188
importPaths.push(node.moduleSpecifier?.getText(sourceFile));
112189
} else if (ts.isCallExpression(node)) {
113190
const text = node.getText(sourceFile);
@@ -120,19 +197,12 @@ function analyzeSoucreFile(
120197
ts.forEachChild(node, getModuleNameText);
121198
}
122199
getModuleNameText(node);
123-
console.log(importPaths);
124200

125201
importPaths.forEach(moduleNameText => {
126202
if (!moduleNameText) {
127-
console.log('moduleNameText is empty');
128203
return;
129204
}
130205
const moduleName = moduleNameText.slice(1, moduleNameText.length - 1); // import 文のクォート及びダブルクォートを除去
131-
console.log(
132-
'ts.resolveModuleName(moduleName, sourceFile.fileName, options, ts.sys).resolvedModule?.resolvedFileName = ',
133-
ts.resolveModuleName(moduleName, sourceFile.fileName, options, ts.sys)
134-
.resolvedModule?.resolvedFileName,
135-
);
136206
const moduleFileFullName =
137207
ts.resolveModuleName(moduleName, sourceFile.fileName, options, ts.sys)
138208
.resolvedModule?.resolvedFileName ?? '';
@@ -142,7 +212,6 @@ function analyzeSoucreFile(
142212
: moduleFileFullName,
143213
);
144214
if (!moduleFilePath) {
145-
console.log('moduleFilePath is empty');
146215
return;
147216
}
148217
const toNode: Node = {
@@ -165,3 +234,43 @@ function analyzeSoucreFile(
165234
return { nodes, relations };
166235
};
167236
}
237+
238+
function getVueAndTsFilePathsRecursive(
239+
dir: string,
240+
mut_filePaths: string[] = [],
241+
): string[] {
242+
const files = fs.readdirSync(dir);
243+
files.forEach(file => {
244+
const filePath = path.join(dir, file);
245+
if (
246+
fs.statSync(filePath).isDirectory() &&
247+
!filePath.includes('node_modules')
248+
) {
249+
// ディレクトリの場合は再帰的に呼び出す
250+
return getVueAndTsFilePathsRecursive(filePath, mut_filePaths);
251+
}
252+
253+
if (
254+
// ts.Extension and vue
255+
[
256+
'.ts',
257+
'.tsx',
258+
'.d.ts',
259+
'.js',
260+
'.jsx',
261+
'.json',
262+
'.tsbuildinfo',
263+
'.mjs',
264+
'.mts',
265+
'.d.mts',
266+
'.cjs',
267+
'.cts',
268+
'.d.cts',
269+
'.vue',
270+
].some(ext => filePath.endsWith(ext))
271+
) {
272+
mut_filePaths.push(filePath);
273+
}
274+
});
275+
return mut_filePaths;
276+
}

0 commit comments

Comments
 (0)