symref
Version:
Static code checker for AI code agents (Windsurf, Cline, etc.)
466 lines • 24.6 kB
JavaScript
import { SyntaxKind } from 'ts-morph';
import * as path from 'node:path';
import { NodeUtils } from '../utils/NodeUtils.js';
/**
* シンボルの定義と参照を検索するクラス
*/
export class SymbolFinder {
/**
* コンストラクタ
* @param project ts-morphのプロジェクトインスタンス
*/
constructor(project) {
this.project = project;
this.nodeUtils = new NodeUtils();
}
/**
* シンボルの定義ノードを見つける
* @param symbolName シンボル名
* @returns 定義ノード(見つからない場合はundefined)
*/
findDefinitionNode(symbolName) {
let definitionNode;
// 定義を探す
for (const sourceFile of this.project.getSourceFiles()) {
// .d.tsファイルはスキップ
if (sourceFile.getFilePath().endsWith('.d.ts'))
continue;
// クラス宣言を直接検索
const classDecl = sourceFile.getClass(symbolName);
if (classDecl) {
// クラスコンポーネントかどうか確認
const nameNode = classDecl.getNameNode();
if (nameNode) {
const symbolType = this.nodeUtils.determineSymbolType(nameNode);
definitionNode = nameNode;
if (definitionNode)
break;
}
}
// インターフェース宣言を直接検索
if (!definitionNode) {
const interfaceDecl = sourceFile.getInterface(symbolName);
if (interfaceDecl) {
definitionNode = interfaceDecl.getNameNode();
if (definitionNode)
break;
}
}
// 関数宣言を直接検索
if (!definitionNode) {
const funcDecl = sourceFile.getFunction(symbolName);
if (funcDecl) {
// 関数コンポーネントかどうか確認
const nameNode = funcDecl.getNameNode();
if (nameNode) {
const symbolType = this.nodeUtils.determineSymbolType(nameNode);
}
definitionNode = funcDecl.getNameNode();
if (definitionNode)
break;
}
}
// Enum宣言を直接検索
if (!definitionNode) {
const enumDecl = sourceFile.getEnum(symbolName);
if (enumDecl) {
definitionNode = enumDecl.getNameNode();
if (definitionNode)
break;
}
}
// 変数宣言 (関数コンポーネントなど) を検索
if (!definitionNode) {
const varDecls = sourceFile.getVariableDeclarations();
for (const varDecl of varDecls) {
if (varDecl.getName() === symbolName) {
// 変数がコンポーネントかどうか判断
const nameNode = varDecl.getNameNode();
if (nameNode) {
const symbolType = this.nodeUtils.determineSymbolType(nameNode);
// 関数コンポーネントかどうかをより詳細に判断
const initializer = varDecl.getInitializer();
if (initializer) {
// Arrow Function または Function Expression
if (initializer.isKind(SyntaxKind.ArrowFunction) || initializer.isKind(SyntaxKind.FunctionExpression)) {
// JSX要素を検索
const jsxElements = [
...initializer.getDescendantsOfKind(SyntaxKind.JsxElement),
...initializer.getDescendantsOfKind(SyntaxKind.JsxSelfClosingElement)
];
if (jsxElements.length <= 0) {
// React Hooksを使用しているか確認
const callExpressions = initializer.getDescendantsOfKind(SyntaxKind.CallExpression);
for (const call of callExpressions) {
const expression = call.getExpression().getText();
if (expression.startsWith('use') && /^use[A-Z]/.test(expression)) {
break;
}
}
}
}
// React.memo, React.forwardRef など
if (initializer.isKind(SyntaxKind.CallExpression)) {
const expression = initializer.getExpression().getText();
}
}
}
definitionNode = varDecl.getNameNode();
if (definitionNode)
break;
}
}
if (definitionNode)
break;
}
// デフォルトエクスポートを検索
if (!definitionNode) {
const defaultExportSymbol = sourceFile.getDefaultExportSymbol();
if (defaultExportSymbol) {
const declarations = defaultExportSymbol.getDeclarations();
// export default symbolName; or export default class/function/const symbolName
declarations.forEach(declaration => {
var _a, _b;
if (declaration.isKind(SyntaxKind.ExportAssignment)) {
const expression = declaration.getExpression();
// 通常の識別子エクスポート (export default Component;)
if (expression.isKind(SyntaxKind.Identifier) && expression.getText() === symbolName) {
// Try to find the original definition of the exported identifier
const originalSymbol = expression.getSymbol();
const originalDeclarations = originalSymbol === null || originalSymbol === void 0 ? void 0 : originalSymbol.getDeclarations();
if (originalDeclarations && originalDeclarations.length > 0) {
// Find the actual definition node (e.g., VariableDeclaration, FunctionDeclaration)
const originalDecl = originalDeclarations[0];
// Try getNameNode() first, then fallback to Identifier descendant
definitionNode = (_b = (_a = originalDecl).getNameNode) === null || _b === void 0 ? void 0 : _b.call(_a);
if (!definitionNode) {
definitionNode = originalDecl.getFirstDescendantByKind(SyntaxKind.Identifier);
}
// If the identifier is not directly found, use the declaration itself (might need refinement)
if (!definitionNode)
definitionNode = originalDecl;
}
}
}
});
}
if (definitionNode)
break;
}
}
// 定義ノードが見つかった場合は詳細をログに出力
if (definitionNode) {
const sourceFile = definitionNode.getSourceFile();
return definitionNode;
}
return undefined;
}
/**
* 定義情報を抽出する
* @param definitionNode 定義ノード
* @returns 定義情報
*/
extractDefinitionInfo(definitionNode) {
const defPos = definitionNode.getSourceFile().getLineAndColumnAtPos(definitionNode.getStart());
const defFilePath = path.relative(process.cwd(), definitionNode.getSourceFile().getFilePath());
const defContext = this.nodeUtils.getNodeContext(definitionNode);
return {
filePath: defFilePath,
line: defPos.line,
column: defPos.column,
context: defContext
};
}
/**
* シンボルの参照を収集する
* @param symbolName シンボル名
* @param definitionNode 定義ノード
* @param includeInternalReferences 内部参照を含めるかどうか
* @returns 参照情報の配列
*/
collectReferences(symbolName, definitionNode, includeInternalReferences = false) {
var _a, _b;
const references = [];
const definitionSourceFile = definitionNode.getSourceFile();
const definitionFilePath = definitionSourceFile.getFilePath();
// isComponentDefinition のようなヘルパーメソッドを NodeUtils に追加することを検討
const isComponent = this.nodeUtils.determineSymbolType(definitionNode).includes('component');
const isClassOrInterface = ((_a = definitionNode.getParent()) === null || _a === void 0 ? void 0 : _a.isKind(SyntaxKind.ClassDeclaration)) || ((_b = definitionNode.getParent()) === null || _b === void 0 ? void 0 : _b.isKind(SyntaxKind.InterfaceDeclaration));
// すべてのソースファイルを検索
for (const sourceFile of this.project.getSourceFiles()) {
const currentFilePath = sourceFile.getFilePath();
// 内部参照を含めない場合は、定義ファイルをスキップ
if (!includeInternalReferences && currentFilePath === definitionFilePath) {
continue;
}
// .d.tsファイルはスキップ
if (currentFilePath.endsWith('.d.ts'))
continue;
// インポート文を検索 (クラス、インターフェース、コンポーネント)
if (isClassOrInterface || isComponent) {
const importDeclarations = sourceFile.getImportDeclarations();
for (const importDecl of importDeclarations) {
const namedImports = importDecl.getNamedImports();
for (const namedImport of namedImports) {
if (namedImport.getName() === symbolName) {
const referenceInfo = this.extractReferenceInfo(namedImport.getNameNode(), currentFilePath, "Import Declaration");
if (referenceInfo)
references.push(referenceInfo);
}
}
// デフォルトインポートもチェック
const defaultImport = importDecl.getDefaultImport();
if (defaultImport && defaultImport.getText() === symbolName) {
const referenceInfo = this.extractReferenceInfo(defaultImport, currentFilePath, "Default Import Declaration");
if (referenceInfo)
references.push(referenceInfo);
}
}
}
// エクスポート宣言(export {}, export default)の検索
const exportDeclarations = sourceFile.getDescendantsOfKind(SyntaxKind.ExportDeclaration);
for (const exportDecl of exportDeclarations) {
const namedExports = exportDecl.getNamedExports();
for (const namedExport of namedExports) {
// export { symbolName } または export { originalName as symbolName }
const exportName = namedExport.getName();
if (exportName === symbolName) {
// エクスポートされるシンボル名が一致
const referenceInfo = this.extractReferenceInfo(namedExport.getNameNode(), currentFilePath, "Export Declaration");
if (referenceInfo)
references.push(referenceInfo);
}
// エクスポートの元の名前がシンボル名と一致(export { symbolName as alias })
const propertyName = namedExport.getAliasNode() ? namedExport.getNameNode().getText() : null;
if (propertyName === symbolName) {
const referenceInfo = this.extractReferenceInfo(namedExport.getNameNode(), currentFilePath, "Export Declaration");
if (referenceInfo)
references.push(referenceInfo);
}
}
}
// デフォルトエクスポート(export default symbolName)の検索
const exportAssignments = sourceFile.getDescendantsOfKind(SyntaxKind.ExportAssignment);
for (const exportAssignment of exportAssignments) {
const expression = exportAssignment.getExpression();
// 識別子の場合(export default Component)
if (expression.isKind(SyntaxKind.Identifier) && expression.getText() === symbolName) {
const referenceInfo = this.extractReferenceInfo(expression, currentFilePath, "Default Export");
if (referenceInfo)
references.push(referenceInfo);
}
// HOCでラップされたコンポーネント(export default memo(Component))
if (expression.isKind(SyntaxKind.CallExpression)) {
const callExpr = expression;
const funcName = callExpr.getExpression().getText();
// React HOC関数(memo, forwardRef等)かチェック
if (funcName === 'memo' || funcName === 'React.memo' ||
funcName === 'forwardRef' || funcName === 'React.forwardRef') {
const args = callExpr.getArguments();
for (const arg of args) {
if (arg.isKind(SyntaxKind.Identifier) && arg.getText() === symbolName) {
// 元の定義を探す
const originalSymbol = arg.getSymbol();
if (originalSymbol) {
const originalDeclarations = originalSymbol.getDeclarations();
if (originalDeclarations && originalDeclarations.length > 0) {
const referenceInfo = this.extractReferenceInfo(arg, currentFilePath, "Default Export (HOC-wrapped)");
if (referenceInfo)
references.push(referenceInfo);
}
}
}
}
}
}
}
// クラス継承関係の検出(extends BaseComponent など)
const heritageClausesWithExtends = sourceFile.getDescendantsOfKind(SyntaxKind.HeritageClause)
.filter(clause => clause.getToken() === SyntaxKind.ExtendsKeyword);
for (const clause of heritageClausesWithExtends) {
for (const typeNode of clause.getTypeNodes()) {
const expression = typeNode.getExpression();
if (expression.isKind(SyntaxKind.Identifier) && expression.getText() === symbolName) {
const referenceInfo = this.extractReferenceInfo(expression, currentFilePath, "Class Extension");
if (referenceInfo)
references.push(referenceInfo);
}
}
}
// シンボル名に一致する識別子を検索
const identifiers = sourceFile.getDescendantsOfKind(SyntaxKind.Identifier)
.filter(node => node.getText() === symbolName && node !== definitionNode); // 定義ノード自体は除外
// 各識別子が有効な参照かどうかをチェック
for (const node of identifiers) {
const isValid = this.nodeUtils.isValidReference(node, definitionNode);
if (isValid) {
const referenceInfo = this.extractReferenceInfo(node, currentFilePath);
if (referenceInfo) {
references.push(referenceInfo);
}
}
}
// JSX タグ名を検索
const jsxElements = [
...sourceFile.getDescendantsOfKind(SyntaxKind.JsxOpeningElement),
...sourceFile.getDescendantsOfKind(SyntaxKind.JsxSelfClosingElement)
];
for (const element of jsxElements) {
// element の型を明示的に指定
const jsxElement = element;
const tagNameNode = jsxElement.getTagNameNode();
let tagName = '';
if (tagNameNode.isKind(SyntaxKind.Identifier)) {
tagName = tagNameNode.getText();
}
else if (tagNameNode.isKind(SyntaxKind.PropertyAccessExpression)) {
// 例: <Namespace.Component />
tagName = tagNameNode.getText(); // フルネームで比較
}
if (tagName === symbolName) {
const isValid = this.nodeUtils.isValidReference(tagNameNode, definitionNode);
// JSXタグは基本的に参照とみなす
const referenceInfo = this.extractReferenceInfo(tagNameNode, currentFilePath, "JSX Element");
if (referenceInfo) {
references.push(referenceInfo);
}
}
}
}
// 重複を除外 (同じ場所で複数回参照される場合など)
const uniqueReferences = Array.from(new Map(references.map(ref => [`${ref.filePath}:${ref.line}:${ref.column}`, ref])).values());
return uniqueReferences;
}
/**
* 定義ノードがクラスまたはインターフェースの定義かどうかを判定する
* @param node 定義ノード
* @returns クラスまたはインターフェースの定義かどうか
*/
isClassOrInterfaceDefinition(node) {
// Note: コンポーネント定義も含むように拡張が必要な場合がある
const parent = node.getParent();
if (!parent)
return false;
return (parent.isKind(SyntaxKind.ClassDeclaration) ||
parent.isKind(SyntaxKind.InterfaceDeclaration)
// || this.isComponentDefinition(node) // ヘルパーメソッドを追加する場合
);
}
/**
* 参照情報を抽出する
* @param node 参照ノード
* @param currentFile 現在のファイルパス
* @param contextPrefix コンテキスト情報の接頭辞 (例: "JSX Element")
* @returns 参照情報
*/
extractReferenceInfo(node, currentFile, contextPrefix) {
try {
const sourceFile = node.getSourceFile();
const pos = sourceFile.getLineAndColumnAtPos(node.getStart());
const relativeFilePath = path.relative(process.cwd(), currentFile);
let context = this.nodeUtils.getNodeContext(node);
if (contextPrefix) {
if (contextPrefix.includes("JSX Element")) {
console.log(`[Debug JSX] Creating reference info for JSX tag: ${node.getText()}`);
}
context = `${contextPrefix}: ${context}`;
}
return {
filePath: relativeFilePath,
line: pos.line,
column: pos.column,
context
};
}
catch (error) {
console.warn(`Warning: Could not extract reference info for node in ${currentFile}: ${error instanceof Error ? error.message : error}`);
return null;
}
}
/**
* シンボルが存在するかどうかを確認する
* @param symbolName シンボル名
* @returns シンボルが存在する場合はtrue、存在しない場合はfalse
*/
hasSymbol(symbolName) {
const definitionNode = this.findDefinitionNode(symbolName);
return definitionNode !== undefined;
}
/**
* プロジェクト内のすべてのエクスポートされたシンボルを取得
* @param filterType 特定のシンボルタイプでフィルタリングする場合(オプション)
* @returns シンボル名とその位置情報のマップ
*/
getAllExportedSymbols(filterType) {
const exportedSymbols = new Map();
// すべてのソースファイルを走査
for (const sourceFile of this.project.getSourceFiles()) {
// .d.tsファイルはスキップ
if (sourceFile.getFilePath().endsWith('.d.ts'))
continue;
// クラス宣言を処理
if (!filterType || filterType === 'class') {
for (const classDecl of sourceFile.getClasses()) {
if (this.isExported(classDecl)) {
const className = classDecl.getName();
if (className) {
const nameNode = classDecl.getNameNode();
if (nameNode) {
exportedSymbols.set(className, this.extractDefinitionInfo(nameNode));
}
}
}
}
}
// インターフェース宣言を処理
if (!filterType || filterType === 'interface') {
for (const interfaceDecl of sourceFile.getInterfaces()) {
if (this.isExported(interfaceDecl)) {
const interfaceName = interfaceDecl.getName();
if (interfaceName) {
const nameNode = interfaceDecl.getNameNode();
if (nameNode) {
exportedSymbols.set(interfaceName, this.extractDefinitionInfo(nameNode));
}
}
}
}
}
// 関数宣言を処理
if (!filterType || filterType === 'function') {
for (const funcDecl of sourceFile.getFunctions()) {
if (this.isExported(funcDecl)) {
const funcName = funcDecl.getName();
if (funcName) {
const nameNode = funcDecl.getNameNode();
if (nameNode) {
exportedSymbols.set(funcName, this.extractDefinitionInfo(nameNode));
}
}
}
}
}
}
return exportedSymbols;
}
/**
* 宣言がエクスポートされているかどうかを確認
* @param node 宣言ノード
* @returns エクスポートされている場合はtrue
*/
isExported(node) {
var _a, _b;
// モディファイアをチェック
const modifiers = (_b = (_a = node).getModifiers) === null || _b === void 0 ? void 0 : _b.call(_a);
if (modifiers === null || modifiers === void 0 ? void 0 : modifiers.some((m) => m.getKind() === SyntaxKind.ExportKeyword)) {
return true;
}
// 親がエクスポート宣言かどうかをチェック
const parent = node.getParent();
if ((parent === null || parent === void 0 ? void 0 : parent.getKind()) === SyntaxKind.ExportDeclaration) {
return true;
}
return false;
}
}
//# sourceMappingURL=SymbolFinder.js.map