Skip to content

Commit 17df8b4

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

File tree

1 file changed

+127
-22
lines changed

1 file changed

+127
-22
lines changed

src/graph/createGraph.ts

Lines changed: 127 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,95 @@ 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+
fs.mkdirSync(path.dirname(tmpFilePath), { recursive: true });
86+
fs.copyFileSync(relativeFilePath, tmpFilePath);
87+
});
88+
89+
const { options, fileNames: fullFilePaths } = ts.parseJsonConfigFileContent(
90+
config,
91+
ts.sys,
92+
path.join(tmpDir, relativeRootDir),
93+
);
94+
// rootDir を設定しない場合、 tmpDir/rootDir である場合に `rootDir/` が node についてしまう
95+
options.rootDir = path.join(tmpDir, relativeRootDir);
96+
// ↑ここまでで、ファイルを tmp にコピーし、新たな fileNames と options を生成する
97+
const program = ts.createProgram(fullFilePaths, options);
98+
function renameNode(node: Node) {
99+
return {
100+
...node,
101+
path: node.path
102+
.replace('.vue.ts', '.vue')
103+
.replace(`${tmpDir.slice(1)}/`, ''),
104+
name: node.name
105+
.replace('.vue.ts', '.vue')
106+
.replace(`${tmpDir.slice(1)}/`, ''),
107+
};
108+
}
44109
const graphs = program
45110
.getSourceFiles()
46111
.filter(sourceFile => !sourceFile.fileName.includes('node_modules')) // node_modules 配下のファイルは除外
47112
.filter(piped(getFilePath(options), removeSlash, isNotMatchSomeExclude))
48-
.map(analyzeSoucreFile(options));
49-
50-
return { graph: mergeGraph(...graphs), meta: { rootDir } };
113+
.map(analyzeSoucreFile(options))
114+
.map(graph => {
115+
// graph においては .vue.ts ファイルを .vue に戻す
116+
return {
117+
nodes: graph.nodes.map(renameNode),
118+
relations: graph.relations.map(relation => {
119+
return {
120+
...relation,
121+
from: renameNode(relation.from),
122+
to: renameNode(relation.to),
123+
};
124+
}),
125+
};
126+
});
127+
return mergeGraph(...graphs);
51128
}
52129

53130
function getName(filePath: string) {
@@ -104,10 +181,6 @@ function analyzeSoucreFile(
104181
const importPaths: (string | undefined)[] = [];
105182
function getModuleNameText(node: ts.Node) {
106183
if (ts.isImportDeclaration(node)) {
107-
console.log(
108-
'isImportDeclaration',
109-
node.moduleSpecifier?.getText(sourceFile),
110-
);
111184
importPaths.push(node.moduleSpecifier?.getText(sourceFile));
112185
} else if (ts.isCallExpression(node)) {
113186
const text = node.getText(sourceFile);
@@ -120,19 +193,12 @@ function analyzeSoucreFile(
120193
ts.forEachChild(node, getModuleNameText);
121194
}
122195
getModuleNameText(node);
123-
console.log(importPaths);
124196

125197
importPaths.forEach(moduleNameText => {
126198
if (!moduleNameText) {
127-
console.log('moduleNameText is empty');
128199
return;
129200
}
130201
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-
);
136202
const moduleFileFullName =
137203
ts.resolveModuleName(moduleName, sourceFile.fileName, options, ts.sys)
138204
.resolvedModule?.resolvedFileName ?? '';
@@ -142,7 +208,6 @@ function analyzeSoucreFile(
142208
: moduleFileFullName,
143209
);
144210
if (!moduleFilePath) {
145-
console.log('moduleFilePath is empty');
146211
return;
147212
}
148213
const toNode: Node = {
@@ -165,3 +230,43 @@ function analyzeSoucreFile(
165230
return { nodes, relations };
166231
};
167232
}
233+
234+
function getVueAndTsFilePathsRecursive(
235+
dir: string,
236+
mut_filePaths: string[] = [],
237+
): string[] {
238+
const files = fs.readdirSync(dir);
239+
files.forEach(file => {
240+
const filePath = path.join(dir, file);
241+
if (
242+
fs.statSync(filePath).isDirectory() &&
243+
!filePath.includes('node_modules')
244+
) {
245+
// ディレクトリの場合は再帰的に呼び出す
246+
return getVueAndTsFilePathsRecursive(filePath, mut_filePaths);
247+
}
248+
249+
if (
250+
// ts.Extension and vue
251+
[
252+
'.ts',
253+
'.tsx',
254+
'.d.ts',
255+
'.js',
256+
'.jsx',
257+
'.json',
258+
'.tsbuildinfo',
259+
'.mjs',
260+
'.mts',
261+
'.d.mts',
262+
'.cjs',
263+
'.cts',
264+
'.d.cts',
265+
'.vue',
266+
].some(ext => filePath.endsWith(ext))
267+
) {
268+
mut_filePaths.push(filePath);
269+
}
270+
});
271+
return mut_filePaths;
272+
}

0 commit comments

Comments
 (0)