dittory 0.0.2 → 0.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +0,0 @@
1
- {"version":3,"file":"analyzeProps-CoEqudRM.mjs","names":["propsParam: Node | undefined","current: Node | undefined","usages: Usage[]","groupedMap: GroupedMap","result: Constant[]","exportedComponents: Exported[]","component: Exported","groupedUsages: Record<string, Usage[]>","results: ClassifiedDeclaration[]","type: DeclarationType"],"sources":["../src/components/getProps.ts","../src/extraction/resolveExpressionValue.ts","../src/extraction/flattenObjectExpression.ts","../src/extraction/hasDisableComment.ts","../src/extraction/extractUsages.ts","../src/source/fileFilters.ts","../src/utils/getSingleValueFromSet.ts","../src/analyzer/baseAnalyzer.ts","../src/analyzer/componentAnalyzer.ts","../src/components/isReactComponent.ts","../src/source/classifyDeclarations.ts","../src/analyzeProps.ts"],"sourcesContent":["import { Node, type Type } from \"ts-morph\";\nimport type { Definition } from \"@/types\";\n\n/**\n * コンポーネントのprops定義を取得する\n *\n * 関数の第一パラメータの型情報からpropsを抽出する。\n * 「Props」などの命名規則に依存せず、TypeScriptの型システムから直接取得するため、\n * どのような命名でもpropsを正確に取得できる。\n *\n * 対応パターン:\n * - function Component(props: Props)\n * - const Component = (props: Props) => ...\n * - React.forwardRef((props, ref) => ...)\n * - React.memo((props) => ...)\n */\nexport function getProps(declaration: Node): Definition[] {\n // 関数宣言、アロー関数、関数式から第一パラメータ(props)を取得\n let propsParam: Node | undefined;\n\n if (Node.isFunctionDeclaration(declaration)) {\n const params = declaration.getParameters();\n if (params.length > 0) {\n propsParam = params[0];\n }\n } else if (Node.isVariableDeclaration(declaration)) {\n const initializer = declaration.getInitializer();\n if (initializer) {\n if (\n Node.isArrowFunction(initializer) ||\n Node.isFunctionExpression(initializer)\n ) {\n const params = initializer.getParameters();\n if (params.length > 0) {\n propsParam = params[0];\n }\n } else if (Node.isCallExpression(initializer)) {\n // React.forwardRef, React.memo などのラッパー関数の場合\n const args = initializer.getArguments();\n for (const arg of args) {\n if (Node.isArrowFunction(arg) || Node.isFunctionExpression(arg)) {\n const params = arg.getParameters();\n if (params.length > 0) {\n propsParam = params[0];\n break;\n }\n }\n }\n }\n }\n }\n\n if (!propsParam) {\n return [];\n }\n\n // パラメータの型情報を取得\n const propsType = propsParam.getType();\n return extractPropsFromType(propsType);\n}\n\n/**\n * 型からprops定義を抽出する\n *\n * Mapを使用する理由:\n * - 交差型(A & B)の場合、同じprop名が複数の型に存在する可能性がある\n * - 最初に見つかった定義を優先し、重複を排除するためにMapを使用\n * - 抽出後にindexを付与してDefinition配列として返す\n */\nfunction extractPropsFromType(type: Type): Definition[] {\n const propsMap = new Map<string, Omit<Definition, \"index\">>();\n\n collectPropsFromType(type, propsMap);\n\n return Array.from(propsMap.values()).map((prop, index) => ({\n ...prop,\n index,\n }));\n}\n\n/**\n * 型からpropsを収集する(交差型の場合は再帰的に処理)\n */\nfunction collectPropsFromType(\n type: Type,\n propsMap: Map<string, Omit<Definition, \"index\">>,\n): void {\n if (type.isIntersection()) {\n for (const intersectionType of type.getIntersectionTypes()) {\n collectPropsFromType(intersectionType, propsMap);\n }\n return;\n }\n\n const properties = type.getProperties();\n for (const prop of properties) {\n const propName = prop.getName();\n const declarations = prop.getDeclarations();\n\n let isOptional = false;\n for (const decl of declarations) {\n if (Node.isPropertySignature(decl) && decl.hasQuestionToken()) {\n isOptional = true;\n break;\n }\n }\n\n // 同じ名前のpropが既に存在する場合は上書きしない(最初に見つかった定義を優先)\n if (!propsMap.has(propName)) {\n propsMap.set(propName, {\n name: propName,\n required: !isOptional,\n });\n }\n }\n}\n","import { Node } from \"ts-morph\";\n\n/**\n * 引数が渡されなかった場合を表す特別な値\n * 必須/任意を問わず、引数未指定の使用箇所を統一的に扱うために使用\n */\nexport const UNDEFINED_VALUE = \"undefined\";\n\n/**\n * 関数型の値を表すプレフィックス\n * コールバック関数など、関数が渡された場合は使用箇所ごとにユニークな値として扱う\n * これにより、同じコールバック関数を渡していても「定数」として検出されない\n */\nexport const FUNCTION_VALUE_PREFIX = \"[function]\";\n\n/**\n * 式の実際の値を解決する\n *\n * 異なるファイルで同じenum値やリテラル値を使用している場合でも、\n * 同一の値として認識できるよう、値を正規化して文字列表現で返す。\n * 同名だが異なる定義(別ファイルの同名enum等)を区別するため、\n * 必要に応じてファイルパスを含めた識別子を返す。\n */\nexport function resolveExpressionValue(expression: Node): string {\n // 関数型の場合は定数として扱わない。\n // 同じ関数参照でも使用箇所ごとに異なる値として扱うことで、\n // コールバック関数が「常に同じ値」として誤検出されるのを防ぐ\n const type = expression.getType();\n if (type.getCallSignatures().length > 0) {\n const sourceFile = expression.getSourceFile();\n const line = expression.getStartLineNumber();\n return `${FUNCTION_VALUE_PREFIX}${sourceFile.getFilePath()}:${line}`;\n }\n\n // PropertyAccessExpression (例: Status.Active) の場合\n // enum参照を正確に識別するために、ファイルパスを含めた完全修飾名を返す\n if (Node.isPropertyAccessExpression(expression)) {\n const symbol = expression.getSymbol();\n const decl = symbol?.getDeclarations()[0];\n\n // enum memberの場合は、同名enumの誤認識を防ぐためファイルパスを含める\n if (decl && Node.isEnumMember(decl)) {\n const enumDecl = decl.getParent();\n if (Node.isEnumDeclaration(enumDecl)) {\n const filePath = enumDecl.getSourceFile().getFilePath();\n const enumName = enumDecl.getName();\n const memberName = decl.getName();\n const value = decl.getValue();\n // 例: \"/path/to/file.ts:Status.Active=0\"\n return `${filePath}:${enumName}.${memberName}=${JSON.stringify(value)}`;\n }\n }\n }\n\n // Identifier (変数参照) の場合\n // 変数の定義元を辿って実際の値を解決する\n if (Node.isIdentifier(expression)) {\n if (expression.getText() === \"undefined\") {\n return UNDEFINED_VALUE;\n }\n\n const symbol = expression.getSymbol();\n const decl = symbol?.getDeclarations()[0];\n\n if (decl && Node.isVariableDeclaration(decl)) {\n const initializer = decl.getInitializer();\n // 初期化子がある場合はその値を使用(空白は正規化して比較しやすくする)\n // 初期化子がない場合は、ファイルパス + 変数名で一意に識別\n return initializer\n ? initializer.getText().replace(/\\s+/g, \" \")\n : `${decl.getSourceFile().getFilePath()}:${expression.getText()}`;\n }\n\n // パラメータや分割代入など、VariableDeclaration以外の宣言の場合\n // ファイルパス + 変数名で識別する\n if (decl) {\n return `${decl.getSourceFile().getFilePath()}:${expression.getText()}`;\n }\n }\n\n // リテラル型の場合は型情報から値を取得\n // JSON.stringifyで文字列と数値を区別できる形式にする(\"foo\" vs 42)\n if (type.isStringLiteral() || type.isNumberLiteral()) {\n return JSON.stringify(type.getLiteralValue());\n }\n\n // booleanリテラルの場合はgetLiteralValue()がundefinedを返すためgetText()を使用\n if (type.isBooleanLiteral()) {\n return type.getText();\n }\n\n // 上記のいずれにも該当しない場合(オブジェクト、配列、テンプレートリテラル等)\n // ソースコードのテキストをそのまま返す\n return expression.getText();\n}\n","import { Node } from \"ts-morph\";\nimport { resolveExpressionValue } from \"@/extraction/resolveExpressionValue\";\n\nexport type FlattenedValue = { key: string; value: string };\n\n/**\n * オブジェクトリテラルを再帰的に解析し、フラットなkey-valueペアを返す\n *\n * @param expression - 解析対象の式ノード\n * @param prefix - キー名のプレフィックス(ネストしたプロパティの親パスを表す)\n * @returns フラット化されたkey-valueペアの配列\n *\n * @example\n * // { a: { b: 1, c: 2 } } → [{ key: \"prefix.a.b\", value: \"1\" }, { key: \"prefix.a.c\", value: \"2\" }]\n */\nexport function flattenObjectExpression(\n expression: Node,\n prefix: string,\n): FlattenedValue[] {\n if (!Node.isObjectLiteralExpression(expression)) {\n // オブジェクトリテラル以外の場合は単一の値として返す\n return [{ key: prefix, value: resolveExpressionValue(expression) }];\n }\n\n return expression.getProperties().flatMap((property) => {\n if (Node.isPropertyAssignment(property)) {\n const propertyName = property.getName();\n const nestedPrefix = prefix ? `${prefix}.${propertyName}` : propertyName;\n const initializer = property.getInitializer();\n\n return initializer\n ? flattenObjectExpression(initializer, nestedPrefix)\n : [];\n }\n\n if (Node.isShorthandPropertyAssignment(property)) {\n // { foo } のような省略形\n const propertyName = property.getName();\n const nestedPrefix = prefix ? `${prefix}.${propertyName}` : propertyName;\n return [\n {\n key: nestedPrefix,\n value: resolveExpressionValue(property.getNameNode()),\n },\n ];\n }\n\n return [];\n });\n}\n","import type { Node } from \"ts-morph\";\n\nconst DISABLE_NEXT_LINE = \"dittory-disable-next-line\";\nconst DISABLE_LINE = \"dittory-disable-line\";\n\n/**\n * ノードに除外コメントがあるかを判定する\n *\n * 以下の2パターンをサポート:\n * - \"dittory-disable-next-line\": 次の行を除外(leading comments をチェック)\n * - \"dittory-disable-line\": 同じ行を除外(trailing comments をチェック)\n *\n * 祖先ノードを辿り、いずれかのノードのコメントに\n * 除外キーワードが含まれていれば除外対象とする。\n *\n * @param node - 判定対象のノード\n * @returns 除外コメントが存在すれば true\n */\nexport function hasDisableComment(node: Node): boolean {\n let current: Node | undefined = node;\n\n while (current) {\n const leadingComments = current.getLeadingCommentRanges();\n const trailingComments = current.getTrailingCommentRanges();\n\n for (const comment of leadingComments) {\n if (comment.getText().includes(DISABLE_NEXT_LINE)) {\n return true;\n }\n }\n\n for (const comment of trailingComments) {\n if (comment.getText().includes(DISABLE_LINE)) {\n return true;\n }\n }\n\n current = current.getParent();\n }\n\n return false;\n}\n","import {\n type CallExpression,\n type JsxAttribute,\n type JsxOpeningElement,\n type JsxSelfClosingElement,\n Node,\n} from \"ts-morph\";\nimport type { Definition, Exported, Usage } from \"@/types\";\nimport { flattenObjectExpression } from \"./flattenObjectExpression\";\nimport { hasDisableComment } from \"./hasDisableComment\";\nimport { UNDEFINED_VALUE } from \"./resolveExpressionValue\";\n\n/**\n * 使用状況を抽出するユーティリティクラス\n */\nexport class ExtractUsages {\n /**\n * 関数呼び出しから引数の使用状況を抽出する\n *\n * オブジェクトリテラルの場合は再帰的にフラット化し、\n * 各プロパティを「引数名.プロパティ名」形式で記録する。\n *\n * @param callExpression - 関数呼び出しノード\n * @param callable - 対象の関数情報\n * @returns 引数使用状況の配列\n */\n static fromCall(callExpression: CallExpression, callable: Exported): Usage[] {\n // dittory-disable-next-line コメントがある場合は除外\n if (hasDisableComment(callExpression)) {\n return [];\n }\n\n const usages: Usage[] = [];\n const sourceFile = callExpression.getSourceFile();\n const args = callExpression.getArguments();\n\n for (const param of callable.definitions) {\n const arg = args[param.index];\n\n if (!arg) {\n // 引数が渡されていない場合はundefinedとして記録\n usages.push({\n name: param.name,\n value: UNDEFINED_VALUE,\n usageFilePath: sourceFile.getFilePath(),\n usageLine: callExpression.getStartLineNumber(),\n });\n continue;\n }\n\n // オブジェクトリテラルの場合は再帰的にフラット化\n for (const { key, value } of flattenObjectExpression(arg, param.name)) {\n usages.push({\n name: key,\n value,\n usageFilePath: sourceFile.getFilePath(),\n usageLine: arg.getStartLineNumber(),\n });\n }\n }\n\n return usages;\n }\n\n /**\n * JSX要素からprops使用状況を抽出する\n *\n * @param element - JSX要素ノード\n * @param definitions - props定義の配列\n * @returns props使用状況の配列\n */\n static fromJsxElement(\n element: JsxOpeningElement | JsxSelfClosingElement,\n definitions: Definition[],\n ): Usage[] {\n // dittory-disable-next-line コメントがある場合は除外\n if (hasDisableComment(element)) {\n return [];\n }\n\n const usages: Usage[] = [];\n const sourceFile = element.getSourceFile();\n\n // JSX属性をMapに変換\n const attributeMap = new Map<string, JsxAttribute>();\n for (const attr of element.getAttributes()) {\n if (Node.isJsxAttribute(attr)) {\n attributeMap.set(attr.getNameNode().getText(), attr);\n }\n }\n\n // definitionsをループして処理\n for (const prop of definitions) {\n const attr = attributeMap.get(prop.name);\n\n if (!attr) {\n // 渡されていない場合(required/optional問わず記録)\n usages.push({\n name: prop.name,\n value: UNDEFINED_VALUE,\n usageFilePath: sourceFile.getFilePath(),\n usageLine: element.getStartLineNumber(),\n });\n continue;\n }\n\n // 属性が渡されている場合、値を抽出\n const initializer = attr.getInitializer();\n\n if (!initializer) {\n // boolean shorthand (例: <Component disabled />)\n usages.push({\n name: prop.name,\n value: \"true\",\n usageFilePath: sourceFile.getFilePath(),\n usageLine: attr.getStartLineNumber(),\n });\n } else if (Node.isJsxExpression(initializer)) {\n // {expression} 形式\n const expression = initializer.getExpression();\n if (!expression) {\n continue;\n }\n for (const { key, value } of flattenObjectExpression(\n expression,\n prop.name,\n )) {\n usages.push({\n name: key,\n value,\n usageFilePath: sourceFile.getFilePath(),\n usageLine: attr.getStartLineNumber(),\n });\n }\n } else {\n // \"string\" 形式\n usages.push({\n name: prop.name,\n value: initializer.getText(),\n usageFilePath: sourceFile.getFilePath(),\n usageLine: attr.getStartLineNumber(),\n });\n }\n }\n\n return usages;\n }\n}\n","/**\n * ファイルパスがテストファイルまたはStorybookファイルかどうかを判定する\n * - 拡張子が .test.* / .spec.* / .stories.* のファイル\n * - __tests__ / __stories__ フォルダ内のファイル\n */\nexport function isTestOrStorybookFile(filePath: string): boolean {\n // 拡張子ベースの判定\n if (/\\.(test|spec|stories)\\.(ts|tsx|js|jsx)$/.test(filePath)) {\n return true;\n }\n\n // フォルダ名ベースの判定\n if (/\\b__tests__\\b|\\b__stories__\\b/.test(filePath)) {\n return true;\n }\n\n return false;\n}\n","/**\n * Setから唯一の値を安全に取得する\n */\nexport function getSingleValueFromSet(values: Set<string>): string {\n if (values.size !== 1) {\n throw new Error(`Expected exactly 1 value, got ${values.size}`);\n }\n const [firstValue] = Array.from(values);\n return firstValue;\n}\n","import { FUNCTION_VALUE_PREFIX } from \"@/extraction/resolveExpressionValue\";\nimport { isTestOrStorybookFile } from \"@/source/fileFilters\";\nimport type {\n AnalysisResult,\n AnalyzerOptions,\n ClassifiedDeclaration,\n Constant,\n Exported,\n FileFilter,\n Usage,\n} from \"@/types\";\nimport { getSingleValueFromSet } from \"@/utils/getSingleValueFromSet\";\n\n/**\n * 使用データのグループ\n * values.size === 1 の場合、そのパラメータは「定数」として検出される\n */\ninterface UsageData {\n values: Set<string>;\n usages: Usage[];\n}\n\n/**\n * 対象ごとの情報(行番号とパラメータ使用状況)\n */\ninterface TargetInfo {\n line: number;\n params: Map<string, UsageData>;\n}\n\n/**\n * 使用状況を階層的にグループ化したマップ\n *\n * 3階層の構造で使用状況を整理する:\n * 1. ソースファイルパス: どのファイルで定義された対象か\n * 2. 対象名: 関数名/コンポーネント名(+ 行番号とパラメータ使用状況)\n *\n * この構造により、定数検出時に効率的に走査できる。\n */\ntype GroupedMap = Map<string, Map<string, TargetInfo>>;\n\n/**\n * 分析処理の基底クラス\n */\nexport abstract class BaseAnalyzer {\n protected shouldExcludeFile: FileFilter;\n protected minUsages: number;\n\n constructor(options: AnalyzerOptions = {}) {\n this.shouldExcludeFile = options.shouldExcludeFile ?? isTestOrStorybookFile;\n this.minUsages = options.minUsages ?? 2;\n }\n\n /**\n * メイン分析処理\n *\n * @param declarations - 事前分類済みの宣言配列\n */\n analyze(declarations: ClassifiedDeclaration[]): AnalysisResult {\n // 1. エクスポートされた対象を収集\n const exported = this.collect(declarations);\n\n // 2. 使用状況をグループ化\n const groupedMap = this.createGroupedMap(exported);\n\n // 3. 常に同じ値が渡されているパラメータを抽出\n const constants = this.extractConstants(groupedMap);\n\n // 4. 結果を構築\n return { constants, exported };\n }\n\n /**\n * エクスポートされた対象を収集する(サブクラスで実装)\n *\n * @param declarations - 事前分類済みの宣言配列\n */\n protected abstract collect(declarations: ClassifiedDeclaration[]): Exported[];\n\n /**\n * 使用状況をグループ化したマップを作成\n */\n private createGroupedMap(exported: Exported[]): GroupedMap {\n const groupedMap: GroupedMap = new Map();\n\n for (const item of exported) {\n let fileMap = groupedMap.get(item.sourceFilePath);\n if (!fileMap) {\n fileMap = new Map();\n groupedMap.set(item.sourceFilePath, fileMap);\n }\n\n const paramMap = new Map<string, UsageData>();\n for (const [paramName, usages] of Object.entries(item.usages)) {\n const values = new Set<string>();\n for (const usage of usages) {\n values.add(usage.value);\n }\n paramMap.set(paramName, { values, usages });\n }\n fileMap.set(item.name, { line: item.sourceLine, params: paramMap });\n }\n\n return groupedMap;\n }\n\n /**\n * 常に同じ値が渡されているパラメータを抽出\n */\n private extractConstants(groupedMap: GroupedMap): Constant[] {\n const result: Constant[] = [];\n\n for (const [sourceFile, targetMap] of groupedMap) {\n for (const [targetName, targetInfo] of targetMap) {\n for (const [paramName, usageData] of targetInfo.params) {\n const isConstant =\n usageData.usages.length >= this.minUsages &&\n usageData.values.size === 1;\n\n if (!isConstant) {\n continue;\n }\n\n const value = getSingleValueFromSet(usageData.values);\n\n // 関数型の値は定数として報告しない\n // (onClickに同じハンドラを渡している等は、デフォルト値化の候補ではない)\n if (value.startsWith(FUNCTION_VALUE_PREFIX)) {\n continue;\n }\n\n result.push({\n targetName,\n targetSourceFile: sourceFile,\n targetLine: targetInfo.line,\n paramName,\n value,\n usages: usageData.usages,\n });\n }\n }\n }\n\n return result;\n }\n}\n","import { Node, SyntaxKind } from \"ts-morph\";\nimport { getProps } from \"@/components/getProps\";\nimport { ExtractUsages } from \"@/extraction/extractUsages\";\nimport type {\n AnalyzerOptions,\n ClassifiedDeclaration,\n Exported,\n Usage,\n} from \"@/types\";\nimport { BaseAnalyzer } from \"./baseAnalyzer\";\n\n/**\n * Reactコンポーネントのprops分析を行うAnalyzer\n *\n * exportされたReactコンポーネントを収集し、各コンポーネントのprops使用状況を分析する。\n * 常に同じ値が渡されているpropsを検出し、定数として報告する。\n *\n * @example\n * ```ts\n * const analyzer = new ComponentAnalyzer({ minUsages: 2 });\n * const result = analyzer.analyze(sourceFiles);\n * console.log(result.constants);\n * ```\n */\nexport class ComponentAnalyzer extends BaseAnalyzer {\n constructor(options: AnalyzerOptions = {}) {\n super(options);\n }\n\n /**\n * 事前分類済みの宣言からReactコンポーネントを収集する\n *\n * @param declarations - 事前分類済みの宣言配列\n * @returns exportされたコンポーネントとその使用状況の配列\n */\n protected collect(declarations: ClassifiedDeclaration[]): Exported[] {\n const exportedComponents: Exported[] = [];\n\n for (const classified of declarations) {\n const { exportName, sourceFile, declaration } = classified;\n\n // FunctionDeclaration または VariableDeclaration のみを処理\n if (\n !Node.isFunctionDeclaration(declaration) &&\n !Node.isVariableDeclaration(declaration)\n ) {\n continue;\n }\n\n // コンポーネントの定義から名前ノードを取得\n const nameNode = declaration.getNameNode();\n if (!nameNode || !Node.isIdentifier(nameNode)) {\n continue;\n }\n\n // 名前ノードから全参照を検索し、除外対象ファイルからの参照をフィルタ\n const references = nameNode\n .findReferences()\n .flatMap((referencedSymbol) => referencedSymbol.getReferences())\n .filter(\n (ref) => !this.shouldExcludeFile(ref.getSourceFile().getFilePath()),\n );\n\n // コンポーネントの宣言からprops定義を取得\n const props = getProps(declaration);\n\n const component: Exported = {\n name: exportName,\n sourceFilePath: sourceFile.getFilePath(),\n sourceLine: declaration.getStartLineNumber(),\n definitions: props,\n declaration,\n usages: {},\n };\n\n // 参照からJSX要素を抽出し、usagesをprop名ごとにグループ化\n const groupedUsages: Record<string, Usage[]> = {};\n for (const reference of references) {\n const refNode = reference.getNode();\n const parent = refNode.getParent();\n if (!parent) {\n continue;\n }\n\n // <Component> または <Component /> の形でJSX要素として使われているかチェック\n const jsxElement =\n parent.asKind(SyntaxKind.JsxOpeningElement) ??\n parent.asKind(SyntaxKind.JsxSelfClosingElement);\n\n if (!jsxElement) {\n continue;\n }\n\n // タグ名ノードが参照ノードと一致するか確認\n const tagNameNode = jsxElement.getTagNameNode();\n if (tagNameNode !== refNode) {\n continue;\n }\n\n // JSX要素からprops使用状況を抽出\n const usages = ExtractUsages.fromJsxElement(\n jsxElement,\n component.definitions,\n );\n for (const usage of usages) {\n if (!groupedUsages[usage.name]) {\n groupedUsages[usage.name] = [];\n }\n groupedUsages[usage.name].push(usage);\n }\n }\n\n component.usages = groupedUsages;\n exportedComponents.push(component);\n }\n\n return exportedComponents;\n }\n}\n","import { Node } from \"ts-morph\";\n\n/**\n * 宣言がReactコンポーネントかどうかを判定する\n * - 関数がJSXを返しているかチェック\n * - React.FC型注釈を持っているかチェック\n */\nexport function isReactComponent(declaration: Node): boolean {\n // 関数宣言の場合\n if (Node.isFunctionDeclaration(declaration)) {\n return containsJsx(declaration);\n }\n\n // 変数宣言の場合 (const Button = ...)\n if (Node.isVariableDeclaration(declaration)) {\n const initializer = declaration.getInitializer();\n if (!initializer) return false;\n\n // アロー関数または関数式の場合\n if (\n Node.isArrowFunction(initializer) ||\n Node.isFunctionExpression(initializer)\n ) {\n return containsJsx(initializer);\n }\n\n // React.forwardRef, React.memo などのラッパー関数の場合\n if (Node.isCallExpression(initializer)) {\n const args = initializer.getArguments();\n for (const arg of args) {\n if (Node.isArrowFunction(arg) || Node.isFunctionExpression(arg)) {\n if (containsJsx(arg)) return true;\n }\n }\n }\n }\n\n return false;\n}\n\n/**\n * ノード内にJSX要素が含まれているかチェック\n * 効率化: 1回のトラバースで全てのJSX要素をチェック\n */\nfunction containsJsx(node: Node): boolean {\n let hasJsx = false;\n\n node.forEachDescendant((descendant) => {\n if (\n Node.isJsxElement(descendant) ||\n Node.isJsxSelfClosingElement(descendant) ||\n Node.isJsxFragment(descendant)\n ) {\n hasJsx = true;\n return true; // 早期終了\n }\n return undefined;\n });\n\n return hasJsx;\n}\n","import { Node, type SourceFile } from \"ts-morph\";\nimport { isReactComponent } from \"@/components/isReactComponent\";\nimport type { ClassifiedDeclaration, DeclarationType } from \"@/types\";\n\n/**\n * ソースファイルからexportされた関数/コンポーネント/クラス宣言を収集し、\n * 種別(react/function/class)を事前に分類する\n *\n * @param sourceFiles - 分析対象のソースファイル配列\n * @returns 分類済みの宣言配列\n */\nexport function classifyDeclarations(\n sourceFiles: SourceFile[],\n): ClassifiedDeclaration[] {\n const results: ClassifiedDeclaration[] = [];\n\n for (const sourceFile of sourceFiles) {\n const exportedDecls = sourceFile.getExportedDeclarations();\n\n for (const [exportName, declarations] of exportedDecls) {\n // 関数宣言または変数宣言(アロー関数/関数式)を見つける\n const funcDecl = declarations.find((decl) => isFunctionLike(decl));\n\n if (funcDecl) {\n if (\n Node.isFunctionDeclaration(funcDecl) ||\n Node.isVariableDeclaration(funcDecl)\n ) {\n const type: DeclarationType = isReactComponent(funcDecl)\n ? \"react\"\n : \"function\";\n results.push({\n exportName,\n sourceFile,\n declaration: funcDecl,\n type,\n });\n }\n continue;\n }\n\n // クラス宣言を見つける\n const classDecl = declarations.find((decl) =>\n Node.isClassDeclaration(decl),\n );\n\n if (classDecl && Node.isClassDeclaration(classDecl)) {\n results.push({\n exportName,\n sourceFile,\n declaration: classDecl,\n type: \"class\",\n });\n }\n }\n }\n\n return results;\n}\n\n/**\n * 宣言が関数的なもの(関数宣言、アロー関数、関数式)かどうかを判定\n */\nfunction isFunctionLike(declaration: Node): boolean {\n if (Node.isFunctionDeclaration(declaration)) {\n return true;\n }\n\n if (Node.isVariableDeclaration(declaration)) {\n const initializer = declaration.getInitializer();\n if (!initializer) {\n return false;\n }\n\n return (\n Node.isArrowFunction(initializer) ||\n Node.isFunctionExpression(initializer) ||\n Node.isCallExpression(initializer) // React.forwardRef, React.memo など\n );\n }\n\n return false;\n}\n","import type { SourceFile } from \"ts-morph\";\nimport { ComponentAnalyzer } from \"@/analyzer/componentAnalyzer\";\nimport { classifyDeclarations } from \"@/source/classifyDeclarations\";\nimport { isTestOrStorybookFile } from \"@/source/fileFilters\";\nimport type { AnalysisResult, FileFilter } from \"@/types\";\n\ninterface AnalyzePropsOptions {\n shouldExcludeFile?: FileFilter;\n minUsages?: number;\n}\n\n/**\n * コンポーネントのprops使用状況を解析し、常に同じ値が渡されているpropsを検出する\n *\n * @param sourceFiles - 解析対象のソースファイル配列\n * @param options - オプション設定\n * @returns 解析結果(定数props、統計情報、exportされたコンポーネント)\n *\n * @example\n * const project = new Project();\n * project.addSourceFilesAtPaths(\"src/**\\/*.tsx\");\n * const result = analyzePropsCore(project.getSourceFiles());\n */\nexport function analyzePropsCore(\n sourceFiles: SourceFile[],\n options: AnalyzePropsOptions = {},\n): AnalysisResult {\n const { shouldExcludeFile = isTestOrStorybookFile, minUsages = 2 } = options;\n\n // 宣言を事前分類し、Reactコンポーネントのみ抽出\n const declarations = classifyDeclarations(sourceFiles);\n const components = declarations.filter((decl) => decl.type === \"react\");\n\n const analyzer = new ComponentAnalyzer({\n shouldExcludeFile,\n minUsages,\n });\n\n return analyzer.analyze(components);\n}\n"],"mappings":"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"}