dittory 0.0.3 → 0.0.5

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.
@@ -0,0 +1 @@
1
+ {"version":3,"file":"callSiteCollector-CGFPm0in.mjs","names":["merged: AnalyzedDeclaration[]","filePath: string","enumName: string","memberName: string","enumValue: string | number | undefined","line: number","expression: string","identifier: string","declarationLine: number","value: boolean","value: string","value: number","functionName: string","path: string","current: Node | undefined","usages: Usage[]","representativeValue: ArgValue","merged: ConstantParam[]","current: Node | undefined","actualDeclaration: Node","callSiteMap: CallSiteMap","missingValues: FlattenedValue[]","result: FlattenedValue[]","VALID_VALUE_TYPES: readonly ValueType[]","analyzed: AnalyzedDeclaration","analyzed: AnalyzedDeclaration","classifiedDeclarations: ClassifiedDeclaration[]","type: DeclarationType","propsParam: Node | undefined","analyzed: AnalyzedDeclaration","resolvedValue: ArgValue | undefined","resolved: ArgValue | undefined","paramNames: string[]"],"sources":["../src/domain/analyzedDeclarations.ts","../src/domain/usagesByParam.ts","../src/domain/argValueClasses.ts","../src/extraction/hasDisableComment.ts","../src/extraction/extractUsages.ts","../src/domain/constantCandidate.ts","../src/domain/constantParams.ts","../src/extraction/parameterUtils.ts","../src/extraction/extractArgValue.ts","../src/extraction/extractObjectType.ts","../src/extraction/expressionResolver.ts","../src/extraction/valueTypeDetector.ts","../src/source/fileFilters.ts","../src/analyzer/baseAnalyzer.ts","../src/analyzer/classMethodAnalyzer.ts","../src/analyzer/functionAnalyzer.ts","../src/react/isReactComponent.ts","../src/source/classifyDeclarations.ts","../src/analyzeFunctions.ts","../src/react/getProps.ts","../src/analyzer/componentAnalyzer.ts","../src/analyzeProps.ts","../src/domain/callSiteInfo.ts","../src/domain/callSiteMap.ts","../src/extraction/callSiteCollector.ts"],"sourcesContent":["import type {\n FunctionDeclaration,\n MethodDeclaration,\n VariableDeclaration,\n} from \"ts-morph\";\nimport type { Definition, UsagesByParam } from \"@/domain/usagesByParam\";\n\n/**\n * 分析対象(エクスポートされた関数/コンポーネント)\n */\nexport interface AnalyzedDeclaration {\n /** クラスメソッドの場合は \"ClassName.methodName\" 形式 */\n name: string;\n sourceFilePath: string;\n sourceLine: number;\n definitions: Definition[];\n declaration: FunctionDeclaration | VariableDeclaration | MethodDeclaration;\n usages: UsagesByParam;\n}\n\n/**\n * 分析対象のコレクション\n */\nexport class AnalyzedDeclarations {\n private readonly items: AnalyzedDeclaration[];\n\n constructor(items: AnalyzedDeclaration[] = []) {\n this.items = items;\n }\n\n /**\n * 複数の AnalyzedDeclarations を結合して新しい AnalyzedDeclarations を作成\n */\n static merge(\n ...declarationsArray: AnalyzedDeclarations[]\n ): AnalyzedDeclarations {\n const merged: AnalyzedDeclaration[] = [];\n for (const declarations of declarationsArray) {\n merged.push(...declarations.items);\n }\n return new AnalyzedDeclarations(merged);\n }\n\n /**\n * 分析対象が存在しないかどうか\n */\n isEmpty(): boolean {\n return this.items.length === 0;\n }\n\n /**\n * 分析対象の件数\n */\n get length(): number {\n return this.items.length;\n }\n\n /**\n * 分析対象を追加\n */\n push(item: AnalyzedDeclaration): void {\n this.items.push(item);\n }\n\n /**\n * 各要素に対して関数を適用し、結果の配列を返す\n */\n map<T>(callback: (item: AnalyzedDeclaration, index: number) => T): T[] {\n return this.items.map(callback);\n }\n\n /**\n * 条件に一致する最初の要素を返す\n */\n find(\n predicate: (item: AnalyzedDeclaration, index: number) => boolean,\n ): AnalyzedDeclaration | undefined {\n return this.items.find(predicate);\n }\n\n /**\n * インデックスで要素を取得\n */\n get(index: number): AnalyzedDeclaration | undefined {\n return this.items[index];\n }\n\n /**\n * イテレーション用\n */\n *[Symbol.iterator](): Iterator<AnalyzedDeclaration> {\n yield* this.items;\n }\n}\n","import type { ArgValue } from \"./argValueClasses\";\n\n/**\n * 使用箇所の共通interface\n * 関数呼び出しやJSX要素で、どのパラメータにどの値が渡されたかを記録する\n */\nexport interface Usage {\n /** ネストしたプロパティの場合は \"param.nested.key\" 形式 */\n name: string;\n /** リテラル値、enum参照、変数参照などを解決した結果 */\n value: ArgValue;\n usageFilePath: string;\n usageLine: number;\n}\n\n/**\n * パラメータ/props定義の共通interface\n */\nexport interface Definition {\n name: string;\n /** 引数リストにおける位置(0始まり) */\n index: number;\n /** ?がなく、デフォルト値もない場合はtrue */\n required: boolean;\n}\n\n/**\n * パラメータ名ごとにUsageをグループ化して管理するクラス\n */\nexport class UsagesByParam extends Map<string, Usage[]> {\n /**\n * Usageを追加する\n */\n add(usage: Usage): void {\n const usages = this.get(usage.name) ?? [];\n usages.push(usage);\n this.set(usage.name, usages);\n }\n\n /**\n * 複数のUsageを追加する\n */\n addAll(usages: readonly Usage[]): void {\n for (const usage of usages) {\n this.add(usage);\n }\n }\n}\n","// ============================================================================\n// ArgValue クラス階層\n// ============================================================================\n\n/**\n * 引数の値を表す基底抽象クラス\n */\nexport abstract class ArgValue {\n /**\n * 生の値を取得する(プレフィックスなし)\n */\n abstract getValue(): string;\n\n /**\n * 値のプレフィックス(空文字の場合はプレフィックスなし)\n */\n protected abstract readonly prefix: string;\n\n /**\n * outputString() でプレフィックスを含めるかどうか\n * - true: プレフィックスを含める(関数、パラメータ参照など)\n * - false: 値のみ(リテラル値)\n */\n protected abstract readonly includePrefixInOutput: boolean;\n\n /**\n * 比較可能な文字列キーに変換する\n * 同じ値かどうかの判定に使用\n */\n toKey(): string {\n return `[${this.prefix}]${this.getValue()}`;\n }\n\n /**\n * 出力用の文字列表現を取得する\n */\n outputString(): string {\n if (this.includePrefixInOutput) {\n return `[${this.prefix}]${this.getValue()}`;\n }\n return this.getValue();\n }\n}\n\n/**\n * リテラル値の基底抽象クラス\n * 出力時はプレフィックスなし、比較キーには [literal] プレフィックスを付ける\n */\nabstract class LiteralArgValue extends ArgValue {\n protected override readonly prefix: string = \"literal\";\n protected override readonly includePrefixInOutput: boolean = false;\n}\n\n/**\n * enum メンバーのリテラル値\n */\nexport class EnumLiteralArgValue extends LiteralArgValue {\n constructor(\n readonly filePath: string,\n readonly enumName: string,\n readonly memberName: string,\n readonly enumValue: string | number | undefined,\n ) {\n super();\n }\n\n getValue(): string {\n return `${this.filePath}:${this.enumName}.${\n this.memberName\n }=${JSON.stringify(this.enumValue)}`;\n }\n\n override outputString(): string {\n return `${this.enumName}.${this.memberName}`;\n }\n}\n\n/**\n * this プロパティアクセスのリテラル値\n */\nexport class ThisLiteralArgValue extends LiteralArgValue {\n constructor(\n readonly filePath: string,\n readonly line: number,\n readonly expression: string,\n ) {\n super();\n }\n\n protected override readonly prefix = \"this\";\n protected override readonly includePrefixInOutput = true;\n\n getValue(): string {\n return `${this.filePath}:${this.line}:${this.expression}`;\n }\n}\n\n/**\n * メソッド呼び出しのリテラル値\n */\nexport class MethodCallLiteralArgValue extends LiteralArgValue {\n constructor(\n readonly filePath: string,\n readonly line: number,\n readonly expression: string,\n ) {\n super();\n }\n\n protected override readonly prefix = \"methodCall\";\n protected override readonly includePrefixInOutput = true;\n\n getValue(): string {\n return `${this.filePath}:${this.line}:${this.expression}`;\n }\n}\n\n/**\n * 変数参照のリテラル値\n */\nexport class VariableLiteralArgValue extends LiteralArgValue {\n constructor(\n readonly filePath: string,\n readonly identifier: string,\n readonly declarationLine: number,\n ) {\n super();\n }\n\n getValue(): string {\n return `${this.filePath}:${this.declarationLine}:${this.identifier}`;\n }\n}\n\n/**\n * boolean リテラル値\n */\nexport class BooleanLiteralArgValue extends LiteralArgValue {\n constructor(readonly value: boolean) {\n super();\n }\n\n getValue(): string {\n return String(this.value);\n }\n}\n\n/**\n * 文字列リテラル値\n */\nexport class StringLiteralArgValue extends LiteralArgValue {\n constructor(readonly value: string) {\n super();\n }\n\n getValue(): string {\n return JSON.stringify(this.value);\n }\n}\n\n/**\n * 数値リテラル値\n */\nexport class NumberLiteralArgValue extends LiteralArgValue {\n constructor(readonly value: number) {\n super();\n }\n\n getValue(): string {\n return JSON.stringify(this.value);\n }\n}\n\n/**\n * JSX boolean shorthand のリテラル値\n */\nexport class JsxShorthandLiteralArgValue extends LiteralArgValue {\n getValue(): string {\n return \"true\";\n }\n}\n\n/**\n * その他のリテラル値(フォールバック)\n */\nexport class OtherLiteralArgValue extends LiteralArgValue {\n constructor(readonly expression: string) {\n super();\n }\n\n getValue(): string {\n return this.expression;\n }\n}\n\n/**\n * 関数型の値\n */\nexport class FunctionArgValue extends ArgValue {\n constructor(\n readonly filePath: string,\n readonly line: number,\n ) {\n super();\n }\n\n protected override readonly prefix = \"function\";\n protected override readonly includePrefixInOutput = true;\n\n getValue(): string {\n return `${this.filePath}:${this.line}`;\n }\n}\n\n/**\n * パラメータ参照の値\n */\nexport class ParamRefArgValue extends ArgValue {\n constructor(\n readonly filePath: string,\n readonly functionName: string,\n readonly path: string,\n readonly line: number,\n ) {\n super();\n }\n\n protected override readonly prefix = \"paramRef\";\n protected override readonly includePrefixInOutput = true;\n\n getValue(): string {\n return `${this.filePath}:${this.functionName}:${this.path}`;\n }\n}\n\n/**\n * undefined の値\n */\nexport class UndefinedArgValue extends ArgValue {\n protected override readonly prefix = \"undefined\";\n protected override readonly includePrefixInOutput = true;\n\n getValue(): string {\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 { AnalyzedDeclaration } from \"@/domain/analyzedDeclarations\";\nimport {\n JsxShorthandLiteralArgValue,\n UndefinedArgValue,\n} from \"@/domain/argValueClasses\";\nimport type { Definition, Usage } from \"@/domain/usagesByParam\";\nimport type { ExpressionResolver } from \"./expressionResolver\";\nimport { hasDisableComment } from \"./hasDisableComment\";\n\n/**\n * 使用状況を抽出するユーティリティクラス\n */\nexport class ExtractUsages {\n /**\n * 関数呼び出しから引数の使用状況を抽出する\n *\n * オブジェクトリテラルの場合は再帰的にフラット化し、\n * 各プロパティを「引数名.プロパティ名」形式で記録する。\n *\n * @param callExpression - 関数呼び出しノード\n * @param declaration - 分析対象の関数情報\n * @param resolver - 式を解決するためのリゾルバ\n * @returns 引数使用状況の配列\n */\n static fromCall(\n callExpression: CallExpression,\n declaration: AnalyzedDeclaration,\n resolver: ExpressionResolver,\n ): 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 definition of declaration.definitions) {\n const arg = args[definition.index];\n\n if (!arg) {\n // 引数が渡されていない場合はundefinedとして記録\n usages.push({\n name: definition.name,\n value: new UndefinedArgValue(),\n usageFilePath: sourceFile.getFilePath(),\n usageLine: callExpression.getStartLineNumber(),\n });\n continue;\n }\n\n // オブジェクトリテラルの場合は再帰的にフラット化\n for (const { key, value } of resolver.flattenObject(\n arg,\n definition.name,\n )) {\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 * @param resolver - 式を解決するためのリゾルバ\n * @returns props使用状況の配列\n */\n static fromJsxElement(\n element: JsxOpeningElement | JsxSelfClosingElement,\n definitions: Definition[],\n resolver: ExpressionResolver,\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 definition of definitions) {\n const attr = attributeMap.get(definition.name);\n\n if (!attr) {\n // 渡されていない場合(required/optional問わず記録)\n usages.push({\n name: definition.name,\n value: new UndefinedArgValue(),\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: definition.name,\n value: new JsxShorthandLiteralArgValue(),\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 resolver.flattenObject(\n expression,\n definition.name,\n )) {\n usages.push({\n name: key,\n value,\n usageFilePath: sourceFile.getFilePath(),\n usageLine: attr.getStartLineNumber(),\n });\n }\n } else {\n // \"string\" 形式 - resolverを通して解決\n usages.push({\n name: definition.name,\n value: resolver.resolve(initializer),\n usageFilePath: sourceFile.getFilePath(),\n usageLine: attr.getStartLineNumber(),\n });\n }\n }\n\n return usages;\n }\n}\n","import type { Usage } from \"@/domain/usagesByParam\";\nimport { type ArgValue, UndefinedArgValue } from \"./argValueClasses\";\n\n/**\n * 定数候補\n *\n * あるパラメータに渡された値を集約し、定数として扱えるかを判定する\n */\nexport class ConstantCandidate {\n /** 値の比較キー(toKey()の結果)のセット */\n private readonly valueKeys: Set<string>;\n /** 代表的な値(定数検出時に使用) */\n readonly representativeValue: ArgValue;\n readonly usages: Usage[];\n\n constructor(usages: Usage[]) {\n this.usages = usages;\n this.valueKeys = new Set<string>();\n let representativeValue: ArgValue = new UndefinedArgValue();\n for (const usage of usages) {\n this.valueKeys.add(usage.value.toKey());\n representativeValue = usage.value;\n }\n this.representativeValue = representativeValue;\n }\n\n /**\n * 定数として認識できるかを判定\n *\n * 条件:\n * 1. 使用回数が最小使用回数以上\n * 2. すべての使用箇所で同じ値(valueKeys.size === 1)\n * 3. Usage数が総呼び出し回数と一致(すべての呼び出しで値が存在)\n * これにより、オプショナルなプロパティが一部の呼び出しでのみ\n * 指定されている場合を定数として誤検出しない\n */\n isConstant(minUsages: number, totalCallCount: number): boolean {\n return (\n this.usages.length >= minUsages &&\n this.valueKeys.size === 1 &&\n this.usages.length === totalCallCount\n );\n }\n}\n","import type { Usage } from \"@/domain/usagesByParam\";\nimport type { ArgValue } from \"./argValueClasses\";\n\n/**\n * 常に同じ値が渡されているパラメータ(デフォルト値化の候補)\n */\nexport interface ConstantParam {\n declarationName: string;\n declarationSourceFile: string;\n declarationLine: number;\n paramName: string;\n value: ArgValue;\n usages: Usage[];\n}\n\n/**\n * 宣言(関数/コンポーネント)ごとにグループ化された定数パラメータ情報\n */\nexport interface GroupedConstantParam {\n declarationName: string;\n declarationSourceFile: string;\n declarationLine: number;\n params: Array<{\n paramName: string;\n value: ArgValue;\n usageCount: number;\n usages: Usage[];\n }>;\n}\n\n/**\n * 定数パラメータのコレクション\n */\nexport class ConstantParams {\n private readonly items: ConstantParam[];\n\n constructor(items: ConstantParam[] = []) {\n this.items = items;\n }\n\n /**\n * 複数の ConstantParams を結合して新しい ConstantParams を作成\n */\n static merge(...paramsArray: ConstantParams[]): ConstantParams {\n const merged: ConstantParam[] = [];\n for (const params of paramsArray) {\n merged.push(...params.items);\n }\n return new ConstantParams(merged);\n }\n\n /**\n * 定数パラメータが存在しないかどうか\n */\n isEmpty(): boolean {\n return this.items.length === 0;\n }\n\n /**\n * 定数パラメータの件数\n */\n get length(): number {\n return this.items.length;\n }\n\n /**\n * 定数パラメータを追加\n */\n push(item: ConstantParam): void {\n this.items.push(item);\n }\n\n /**\n * 条件に一致する最初の定数パラメータを返す\n */\n find(\n predicate: (item: ConstantParam, index: number) => boolean,\n ): ConstantParam | undefined {\n return this.items.find(predicate);\n }\n\n /**\n * 宣言(関数/コンポーネント)ごとにグループ化\n */\n groupByDeclaration(): GroupedConstantParam[] {\n const groupMap = new Map<string, GroupedConstantParam>();\n\n for (const constantParam of this.items) {\n const key = `${constantParam.declarationSourceFile}:${constantParam.declarationName}`;\n\n let constantParamGroup = groupMap.get(key);\n if (!constantParamGroup) {\n constantParamGroup = {\n declarationName: constantParam.declarationName,\n declarationSourceFile: constantParam.declarationSourceFile,\n declarationLine: constantParam.declarationLine,\n params: [],\n };\n groupMap.set(key, constantParamGroup);\n }\n\n constantParamGroup.params.push({\n paramName: constantParam.paramName,\n value: constantParam.value,\n usageCount: constantParam.usages.length,\n usages: constantParam.usages,\n });\n }\n\n return Array.from(groupMap.values());\n }\n\n /**\n * イテレーション用\n */\n *[Symbol.iterator](): Iterator<ConstantParam> {\n yield* this.items;\n }\n}\n","import { Node, SyntaxKind } from \"ts-morph\";\nimport {\n type ArgValue,\n OtherLiteralArgValue,\n ParamRefArgValue,\n} from \"@/domain/argValueClasses\";\n\n/**\n * 式がパラメータ(関数の引数)を参照しているかどうかを判定する\n * ネストしたプロパティアクセス(例: props.nested.value)にも対応\n */\nexport function isParameterReference(expression: Node): boolean {\n if (Node.isIdentifier(expression)) {\n const symbol = expression.getSymbol();\n const declaration = symbol?.getDeclarations()[0];\n if (!declaration) return false;\n const kind = declaration.getKind();\n return kind === SyntaxKind.Parameter || kind === SyntaxKind.BindingElement;\n }\n if (Node.isPropertyAccessExpression(expression)) {\n return isParameterReference(expression.getExpression());\n }\n return false;\n}\n\n/**\n * 式を含む関数宣言を見つける\n */\nfunction findContainingFunction(node: Node): Node | undefined {\n let current: Node | undefined = node;\n while (current) {\n if (\n Node.isFunctionDeclaration(current) ||\n Node.isArrowFunction(current) ||\n Node.isFunctionExpression(current) ||\n Node.isMethodDeclaration(current)\n ) {\n return current;\n }\n current = current.getParent();\n }\n return undefined;\n}\n\n/**\n * 関数スコープから関数名を取得する\n */\nfunction getFunctionName(functionScope: Node): string {\n if (Node.isFunctionDeclaration(functionScope)) {\n return functionScope.getName() ?? \"anonymous\";\n }\n if (\n Node.isArrowFunction(functionScope) ||\n Node.isFunctionExpression(functionScope)\n ) {\n const parent = functionScope.getParent();\n if (parent && Node.isVariableDeclaration(parent)) {\n return parent.getName();\n }\n return \"anonymous\";\n }\n if (Node.isMethodDeclaration(functionScope)) {\n const className = functionScope\n .getParent()\n ?.asKind(SyntaxKind.ClassDeclaration)\n ?.getName();\n const methodName = functionScope.getName();\n return className ? `${className}.${methodName}` : methodName;\n }\n return \"anonymous\";\n}\n\n/**\n * 式からパラメータパスを構築\n * 例: props.nested.value → \"props.nested.value\"\n */\nfunction buildParameterPath(expression: Node): string {\n if (Node.isIdentifier(expression)) {\n return expression.getText();\n }\n if (Node.isPropertyAccessExpression(expression)) {\n const left = buildParameterPath(expression.getExpression());\n const right = expression.getName();\n return `${left}.${right}`;\n }\n return expression.getText();\n}\n\n/**\n * パラメータ参照の ArgValue を作成する\n */\nexport function createParamRefValue(expression: Node): ArgValue {\n const sourceFile = expression.getSourceFile();\n const filePath = sourceFile.getFilePath();\n const functionScope = findContainingFunction(expression);\n\n if (!functionScope) {\n return new OtherLiteralArgValue(expression.getText());\n }\n\n const functionName = getFunctionName(functionScope);\n const path = buildParameterPath(expression);\n const line = expression.getStartLineNumber();\n\n return new ParamRefArgValue(filePath, functionName, path, line);\n}\n","import { Node, SyntaxKind } from \"ts-morph\";\nimport {\n type ArgValue,\n BooleanLiteralArgValue,\n EnumLiteralArgValue,\n FunctionArgValue,\n JsxShorthandLiteralArgValue,\n MethodCallLiteralArgValue,\n NumberLiteralArgValue,\n OtherLiteralArgValue,\n StringLiteralArgValue,\n ThisLiteralArgValue,\n UndefinedArgValue,\n VariableLiteralArgValue,\n} from \"@/domain/argValueClasses\";\nimport { createParamRefValue, isParameterReference } from \"./parameterUtils\";\n\n/**\n * 式がthisキーワードへのプロパティアクセスかどうかを判定する\n * ネストしたアクセス(例: this.logger.name)にも対応\n */\nfunction isThisPropertyAccess(expression: Node): boolean {\n if (Node.isPropertyAccessExpression(expression)) {\n const baseExpr = expression.getExpression();\n if (baseExpr.getKind() === SyntaxKind.ThisKeyword) {\n return true;\n }\n return isThisPropertyAccess(baseExpr);\n }\n return false;\n}\n\n/**\n * 式から ArgValue を抽出する\n *\n * 式の値を型安全な ArgValue として返す。\n * 呼び出し情報収集時および式の値解決時に使用する。\n *\n * @param expression - 解析対象の式ノード(undefinedの場合はUndefinedArgValueを返す)\n * @returns 式の値を表す ArgValue\n */\nexport function extractArgValue(expression: Node | undefined): ArgValue {\n if (!expression) {\n return new UndefinedArgValue();\n }\n\n // JsxAttribute の場合(例: <Button disabled /> や <Button label=\"hello\" />)\n if (Node.isJsxAttribute(expression)) {\n const initializer = expression.getInitializer();\n if (!initializer) {\n // boolean shorthand(例: <Button disabled />)\n return new JsxShorthandLiteralArgValue();\n }\n if (Node.isJsxExpression(initializer)) {\n // JSX式(例: <Button onClick={handleClick} />)\n return extractArgValue(initializer.getExpression());\n }\n if (Node.isStringLiteral(initializer)) {\n // 文字列値(例: <Button label=\"hello\" />)\n return new StringLiteralArgValue(initializer.getLiteralValue());\n }\n return new OtherLiteralArgValue(initializer.getText());\n }\n\n const type = expression.getType();\n\n // 関数型の場合\n if (type.getCallSignatures().length > 0) {\n const sourceFile = expression.getSourceFile();\n const line = expression.getStartLineNumber();\n return new FunctionArgValue(sourceFile.getFilePath(), line);\n }\n\n // PropertyAccessExpression (例: Status.Active, props.number)\n if (Node.isPropertyAccessExpression(expression)) {\n const symbol = expression.getSymbol();\n const declaration = symbol?.getDeclarations()[0];\n\n // enum memberの場合\n if (declaration && Node.isEnumMember(declaration)) {\n const enumDeclaration = declaration.getParent();\n if (Node.isEnumDeclaration(enumDeclaration)) {\n return new EnumLiteralArgValue(\n enumDeclaration.getSourceFile().getFilePath(),\n enumDeclaration.getName(),\n declaration.getName(),\n declaration.getValue(),\n );\n }\n }\n\n // パラメータのプロパティアクセスの場合\n if (isParameterReference(expression.getExpression())) {\n return createParamRefValue(expression);\n }\n\n // thisキーワードへのプロパティアクセスの場合\n // クラスメンバーは実行時にインスタンスごとに異なる値を持つ可能性があるため、\n // 使用箇所ごとにユニークな値として扱う\n if (isThisPropertyAccess(expression)) {\n return new ThisLiteralArgValue(\n expression.getSourceFile().getFilePath(),\n expression.getStartLineNumber(),\n expression.getText(),\n );\n }\n }\n\n // Identifier (変数参照)\n if (Node.isIdentifier(expression)) {\n if (expression.getText() === \"undefined\") {\n return new UndefinedArgValue();\n }\n\n const symbol = expression.getSymbol();\n const resolvedSymbol = symbol?.getAliasedSymbol() ?? symbol;\n const declaration = resolvedSymbol?.getDeclarations()[0];\n\n if (declaration) {\n let actualDeclaration: Node = declaration;\n let kind = declaration.getKind();\n\n // ShorthandPropertyAssignment の場合、実際の参照先を解決する\n // 例: { name } という短縮プロパティで、name が for...of の分割代入から来ている場合、\n // シンボルの宣言は ShorthandPropertyAssignment 自体になるが、\n // 実際には BindingElement や Parameter や VariableDeclaration を参照している\n if (kind === SyntaxKind.ShorthandPropertyAssignment) {\n const definitions = expression.getDefinitions();\n for (const def of definitions) {\n const node = def.getDeclarationNode();\n if (!node) continue;\n const k = node.getKind();\n if (\n k === SyntaxKind.BindingElement ||\n k === SyntaxKind.Parameter ||\n k === SyntaxKind.VariableDeclaration\n ) {\n actualDeclaration = node;\n kind = k;\n break;\n }\n }\n }\n\n // パラメータまたはBindingElementの場合\n if (kind === SyntaxKind.Parameter || kind === SyntaxKind.BindingElement) {\n return createParamRefValue(expression);\n }\n\n // 変数宣言の場合は初期化子を再帰的に解決\n if (Node.isVariableDeclaration(actualDeclaration)) {\n const initializer = actualDeclaration.getInitializer();\n if (initializer) {\n return extractArgValue(initializer);\n }\n return new VariableLiteralArgValue(\n actualDeclaration.getSourceFile().getFilePath(),\n expression.getText(),\n actualDeclaration.getStartLineNumber(),\n );\n }\n\n // その他の宣言タイプ(インポート宣言など)\n // ファイルパス + 宣言行番号 + 変数名で識別する\n return new VariableLiteralArgValue(\n actualDeclaration.getSourceFile().getFilePath(),\n expression.getText(),\n actualDeclaration.getStartLineNumber(),\n );\n }\n }\n\n // リテラル型\n const literalValue = type.getLiteralValue();\n if (type.isStringLiteral() && typeof literalValue === \"string\") {\n return new StringLiteralArgValue(literalValue);\n }\n\n if (type.isNumberLiteral() && typeof literalValue === \"number\") {\n return new NumberLiteralArgValue(literalValue);\n }\n\n if (type.isBooleanLiteral()) {\n return new BooleanLiteralArgValue(type.getText() === \"true\");\n }\n\n // CallExpression (例: this.method(), obj.method(), func(arg))\n // 以下の場合は実行時に異なる値を返す可能性があるため、使用箇所ごとにユニークな値として扱う:\n // 1. プロパティアクセスを伴うメソッド呼び出し(例: declSourceFile.getFilePath())\n // 2. 引数にパラメータ参照が含まれる関数呼び出し(例: extractArgValue(node))\n if (Node.isCallExpression(expression)) {\n const calleeExpr = expression.getExpression();\n const hasParamRefArg = expression\n .getArguments()\n .some((arg) => isParameterReference(arg));\n\n if (Node.isPropertyAccessExpression(calleeExpr) || hasParamRefArg) {\n return new MethodCallLiteralArgValue(\n expression.getSourceFile().getFilePath(),\n expression.getStartLineNumber(),\n expression.getText(),\n );\n }\n }\n\n return new OtherLiteralArgValue(expression.getText());\n}\n","import type { Type } from \"ts-morph\";\n\n/**\n * 型からオブジェクト部分を抽出する\n * ユニオン型(例: `{ a: number } | undefined`)から `undefined` を除外して\n * オブジェクト型部分を返す。オブジェクト型でない場合は null を返す。\n */\nexport function extractObjectType(type: Type): Type | null {\n // ユニオン型の場合、undefined/null を除外したオブジェクト型を探す\n if (type.isUnion()) {\n for (const unionType of type.getUnionTypes()) {\n if (unionType.isUndefined() || unionType.isNull()) {\n continue;\n }\n const objType = extractObjectType(unionType);\n if (objType) {\n return objType;\n }\n }\n return null;\n }\n\n // プリミティブ型は除外\n if (\n type.isString() ||\n type.isNumber() ||\n type.isBoolean() ||\n type.isUndefined() ||\n type.isNull() ||\n type.isLiteral()\n ) {\n return null;\n }\n\n // 配列型は除外\n if (type.isArray()) {\n return null;\n }\n\n // オブジェクト型かつプロパティを持つ場合\n if (type.isObject() && type.getProperties().length > 0) {\n return type;\n }\n\n return null;\n}\n","import { Node, type ObjectLiteralExpression, type Type } from \"ts-morph\";\nimport {\n type ArgValue,\n ParamRefArgValue,\n UndefinedArgValue,\n} from \"@/domain/argValueClasses\";\nimport type { CallSiteMap } from \"@/domain/callSiteMap\";\nimport { extractArgValue } from \"./extractArgValue\";\nimport { extractObjectType } from \"./extractObjectType\";\n\n/**\n * フラット化された値\n */\nexport type FlattenedValue = { key: string; value: ArgValue };\n\n/**\n * 式の値を解決するクラス\n * CallSiteMap を使ってパラメータ参照を解決し、ArgValueを返す\n */\nexport class ExpressionResolver {\n constructor(private callSiteMap: CallSiteMap) {}\n\n /**\n * 式の実際の値を解決する\n *\n * 異なるファイルで同じenum値やリテラル値を使用している場合でも、\n * 同一の値として認識できるよう、ArgValueとして返す。\n * パラメータ参照の場合はcallSiteMapを使って解決を試みる。\n */\n resolve(expression: Node): ArgValue {\n const argValue = extractArgValue(expression);\n\n // パラメータ参照は callSiteMap を使って解決\n if (argValue instanceof ParamRefArgValue) {\n return this.callSiteMap.resolveParamRef(argValue);\n }\n\n return argValue;\n }\n\n /**\n * オブジェクトリテラルを再帰的に解析し、フラットなkey-valueペアを返す\n *\n * 期待される型(コンテキスト型)から省略されたプロパティも検出し、\n * [undefined] として記録する。\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 */\n flattenObject(expression: Node, prefix: string): FlattenedValue[] {\n if (!Node.isObjectLiteralExpression(expression)) {\n // オブジェクトリテラル以外の場合は単一の値として返す\n return [{ key: prefix, value: this.resolve(expression) }];\n }\n\n // 渡されたプロパティを収集\n const existingValues = this.flattenExistingProperties(expression, prefix);\n\n // 期待される型から省略されたプロパティを検出\n // ユニオン型(例: `{ a: number } | undefined`)からオブジェクト型を抽出\n const contextualType = expression.getContextualType();\n const objectType = contextualType\n ? extractObjectType(contextualType)\n : null;\n const missingValues = objectType\n ? getMissingProperties(expression, objectType, prefix)\n : [];\n\n return [...existingValues, ...missingValues];\n }\n\n /**\n * オブジェクトリテラル内の既存プロパティをフラット化する\n */\n private flattenExistingProperties(\n expression: ObjectLiteralExpression,\n prefix: string,\n ): FlattenedValue[] {\n return expression.getProperties().flatMap((property) => {\n if (Node.isPropertyAssignment(property)) {\n const propertyName = property.getName();\n const nestedPrefix = prefix\n ? `${prefix}.${propertyName}`\n : propertyName;\n const initializer = property.getInitializer();\n\n return initializer ? this.flattenObject(initializer, nestedPrefix) : [];\n }\n\n if (Node.isShorthandPropertyAssignment(property)) {\n // { foo } のような省略形\n const propertyName = property.getName();\n const nestedPrefix = prefix\n ? `${prefix}.${propertyName}`\n : propertyName;\n return [\n {\n key: nestedPrefix,\n value: this.resolve(property.getNameNode()),\n },\n ];\n }\n\n return [];\n });\n }\n}\n\n/**\n * 期待される型と比較して、省略されたプロパティを検出する\n *\n * 省略されたプロパティがオブジェクト型の場合、そのネストプロパティも\n * 再帰的に UndefinedArgValue として出力する。これにより、親プロパティが\n * 省略された場合でも、ネストプロパティが他の呼び出しと比較可能になる。\n */\nfunction getMissingProperties(\n objectExpression: ObjectLiteralExpression,\n expectedType: Type,\n prefix: string,\n): FlattenedValue[] {\n // 渡されたプロパティ名を収集\n const existingPropNames = new Set<string>();\n for (const property of objectExpression.getProperties()) {\n if (Node.isPropertyAssignment(property)) {\n existingPropNames.add(property.getName());\n } else if (Node.isShorthandPropertyAssignment(property)) {\n existingPropNames.add(property.getName());\n }\n }\n\n const missingValues: FlattenedValue[] = [];\n\n // 期待される型のプロパティを走査\n for (const propSymbol of expectedType.getProperties()) {\n const propName = propSymbol.getName();\n\n // 既に渡されているプロパティはスキップ\n if (existingPropNames.has(propName)) {\n continue;\n }\n\n const nestedPrefix = prefix ? `${prefix}.${propName}` : propName;\n\n // 省略されたプロパティを UndefinedArgValue として記録\n missingValues.push({\n key: nestedPrefix,\n value: new UndefinedArgValue(),\n });\n\n // 省略されたプロパティがオブジェクト型の場合、ネストプロパティも再帰的に出力\n const propType = propSymbol.getValueDeclaration()?.getType();\n if (propType) {\n const objType = extractObjectType(propType);\n if (objType) {\n missingValues.push(\n ...getNestedMissingProperties(objType, nestedPrefix),\n );\n }\n }\n }\n\n return missingValues;\n}\n\n/**\n * 省略された親プロパティのネストプロパティを再帰的に UndefinedArgValue として出力\n */\nfunction getNestedMissingProperties(\n parentType: Type,\n prefix: string,\n): FlattenedValue[] {\n const result: FlattenedValue[] = [];\n\n for (const propSymbol of parentType.getProperties()) {\n const propName = propSymbol.getName();\n const nestedPrefix = `${prefix}.${propName}`;\n\n result.push({\n key: nestedPrefix,\n value: new UndefinedArgValue(),\n });\n\n // さらにネストされたオブジェクト型があれば再帰\n const propType = propSymbol.getValueDeclaration()?.getType();\n if (propType) {\n const objType = extractObjectType(propType);\n if (objType) {\n result.push(...getNestedMissingProperties(objType, nestedPrefix));\n }\n }\n }\n\n return result;\n}\n","import {\n type ArgValue,\n BooleanLiteralArgValue,\n EnumLiteralArgValue,\n NumberLiteralArgValue,\n StringLiteralArgValue,\n UndefinedArgValue,\n} from \"@/domain/argValueClasses\";\n\n/**\n * フィルタリング対象の値種別\n */\nexport type ValueType = \"boolean\" | \"number\" | \"string\" | \"enum\" | \"undefined\";\n\n/**\n * 有効な値種別の一覧\n */\nexport const VALID_VALUE_TYPES: readonly ValueType[] = [\n \"boolean\",\n \"number\",\n \"string\",\n \"enum\",\n \"undefined\",\n];\n\n/**\n * ArgValueから種別を判定する\n *\n * @param value - ArgValue インスタンス\n * @returns 検出された種別、または判定不能な場合は null\n */\nfunction detectValueType(value: ArgValue): ValueType | null {\n if (value instanceof BooleanLiteralArgValue) {\n return \"boolean\";\n }\n\n if (value instanceof UndefinedArgValue) {\n return \"undefined\";\n }\n\n if (value instanceof EnumLiteralArgValue) {\n return \"enum\";\n }\n\n if (value instanceof StringLiteralArgValue) {\n return \"string\";\n }\n\n if (value instanceof NumberLiteralArgValue) {\n return \"number\";\n }\n\n // 判定不能(function, paramRef, this, 変数参照など)\n return null;\n}\n\n/**\n * 値が指定された種別に含まれるか判定する\n *\n * @param value - ArgValue インスタンス\n * @param allowedTypes - 許可する種別の配列、または \"all\"\n * @returns 指定種別に含まれる場合は true\n */\nexport function matchesValueTypes(\n value: ArgValue,\n allowedTypes: ValueType[] | \"all\",\n): boolean {\n if (allowedTypes === \"all\") {\n return true;\n }\n\n const detectedType = detectValueType(value);\n if (detectedType === null) {\n // 判定不能な値は種別指定時にはマッチしない\n return false;\n }\n\n return allowedTypes.includes(detectedType);\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","import {\n type Identifier,\n Node,\n type ParameterDeclaration,\n type ReferencedSymbol,\n} from \"ts-morph\";\nimport type { AnalysisResult } from \"@/domain/analysisResult\";\nimport type {\n AnalyzedDeclaration,\n AnalyzedDeclarations,\n} from \"@/domain/analyzedDeclarations\";\nimport type { AnalyzerOptions, FileFilter } from \"@/domain/analyzerOptions\";\nimport {\n FunctionArgValue,\n MethodCallLiteralArgValue,\n ParamRefArgValue,\n} from \"@/domain/argValueClasses\";\nimport type { CallSiteMap } from \"@/domain/callSiteMap\";\nimport type { ClassifiedDeclaration } from \"@/domain/classifiedDeclaration\";\nimport { ConstantCandidate } from \"@/domain/constantCandidate\";\nimport { ConstantParams } from \"@/domain/constantParams\";\nimport type { Definition } from \"@/domain/usagesByParam\";\nimport { ExpressionResolver } from \"@/extraction/expressionResolver\";\nimport {\n matchesValueTypes,\n type ValueType,\n} from \"@/extraction/valueTypeDetector\";\nimport { isTestOrStorybookFile } from \"@/source/fileFilters\";\n\n/**\n * ts-morph の参照情報を表す型\n */\ntype ReferenceEntry = ReturnType<ReferencedSymbol[\"getReferences\"]>[number];\n\n/**\n * 宣言ごとの使用状況プロファイル(行番号とパラメータ使用状況)\n */\nclass DeclarationUsageProfile {\n readonly sourceLine: number;\n private readonly candidatesByParam: Map<string, ConstantCandidate>;\n /** 総呼び出し回数(ネストしたプロパティの存在チェックに使用) */\n private readonly totalCallCount: number;\n\n constructor(\n sourceLine: number,\n candidatesByParam: Map<string, ConstantCandidate>,\n totalCallCount: number,\n ) {\n this.sourceLine = sourceLine;\n this.candidatesByParam = candidatesByParam;\n this.totalCallCount = totalCallCount;\n }\n\n /**\n * 定数として認識されるパラメータを返す\n */\n *findConstantParams(\n minUsages: number,\n ): IterableIterator<[string, ConstantCandidate]> {\n for (const [paramName, candidate] of this.candidatesByParam) {\n if (candidate.isConstant(minUsages, this.totalCallCount)) {\n yield [paramName, candidate];\n }\n }\n }\n}\n\n/**\n * ファイル内の宣言(関数/コンポーネント)を管理するレジストリ\n */\nclass DeclarationRegistry extends Map<string, DeclarationUsageProfile> {\n /**\n * AnalyzedDeclaration から DeclarationUsageProfile を作成して追加\n */\n addFromDeclaration(analyzedDeclaration: AnalyzedDeclaration): void {\n const paramMap = new Map<string, ConstantCandidate>();\n for (const [paramName, usages] of analyzedDeclaration.usages.entries()) {\n paramMap.set(paramName, new ConstantCandidate(usages));\n }\n\n // 総呼び出し回数を計算(最大のUsage配列の長さを使用)\n // すべての呼び出しで存在するパラメータのUsage数が基準となる\n const totalCallCount = Math.max(\n ...[...analyzedDeclaration.usages.values()].map(\n (usages) => usages.length,\n ),\n 0,\n );\n\n this.set(\n analyzedDeclaration.name,\n new DeclarationUsageProfile(\n analyzedDeclaration.sourceLine,\n paramMap,\n totalCallCount,\n ),\n );\n }\n}\n\n/**\n * 使用状況を階層的に管理するレジストリ\n *\n * 2階層の構造で使用状況を整理する:\n * 1. ソースファイルパス: どのファイルで定義された宣言か\n * 2. 宣言名: 関数名/コンポーネント名(+ 行番号とパラメータ使用状況)\n *\n * この構造により、定数検出時に効率的に走査できる。\n */\nclass UsageRegistry extends Map<string, DeclarationRegistry> {\n /**\n * DeclarationRegistry を取得(存在しなければ作成)\n */\n private getOrCreateDeclarationRegistry(\n sourceFilePath: string,\n ): DeclarationRegistry {\n let declarationRegistry = this.get(sourceFilePath);\n if (!declarationRegistry) {\n declarationRegistry = new DeclarationRegistry();\n this.set(sourceFilePath, declarationRegistry);\n }\n return declarationRegistry;\n }\n\n /**\n * AnalyzedDeclarations から UsageRegistry を作成\n */\n static fromDeclarations(declarations: AnalyzedDeclarations): UsageRegistry {\n const usageRegistry = new UsageRegistry();\n for (const analyzedDeclaration of declarations) {\n usageRegistry\n .getOrCreateDeclarationRegistry(analyzedDeclaration.sourceFilePath)\n .addFromDeclaration(analyzedDeclaration);\n }\n return usageRegistry;\n }\n\n /**\n * すべての定数パラメータをフラットに取得\n */\n *findAllConstantParams(minUsages: number): IterableIterator<{\n sourceFile: string;\n declarationName: string;\n declarationLine: number;\n paramName: string;\n candidate: ConstantCandidate;\n }> {\n for (const [sourceFile, declarationRegistry] of this) {\n for (const [declarationName, usageProfile] of declarationRegistry) {\n for (const [paramName, candidate] of usageProfile.findConstantParams(\n minUsages,\n )) {\n yield {\n sourceFile,\n declarationName,\n declarationLine: usageProfile.sourceLine,\n paramName,\n candidate,\n };\n }\n }\n }\n }\n}\n\n/**\n * 分析処理の基底クラス\n */\nexport abstract class BaseAnalyzer {\n protected shouldExcludeFile: FileFilter;\n protected minUsages: number;\n protected allowedValueTypes: ValueType[] | \"all\";\n protected callSiteMap!: CallSiteMap;\n\n constructor(options: AnalyzerOptions = {}) {\n this.shouldExcludeFile = options.shouldExcludeFile ?? isTestOrStorybookFile;\n this.minUsages = options.minUsages ?? 2;\n this.allowedValueTypes = options.allowedValueTypes ?? \"all\";\n }\n\n /**\n * 呼び出し情報を設定する\n * パラメータ経由で渡された値を解決するために使用\n *\n * @param callSiteMap - 呼び出し情報マップ\n */\n setCallSiteMap(callSiteMap: CallSiteMap): void {\n this.callSiteMap = callSiteMap;\n }\n\n /**\n * 式のリゾルバを取得する\n */\n protected getExpressionResolver(): ExpressionResolver {\n return new ExpressionResolver(this.callSiteMap);\n }\n\n /**\n * 識別子から全参照を検索し、除外対象ファイルからの参照をフィルタリングする\n *\n * @param nameNode - 検索対象の識別子ノード\n * @returns フィルタリングされた参照エントリの配列\n */\n protected findFilteredReferences(nameNode: Identifier): ReferenceEntry[] {\n return nameNode\n .findReferences()\n .flatMap((referencedSymbol) => referencedSymbol.getReferences())\n .filter(\n (ref) => !this.shouldExcludeFile(ref.getSourceFile().getFilePath()),\n );\n }\n\n /**\n * ノードからパラメータ定義を取得する\n *\n * FunctionDeclaration, MethodDeclaration, VariableDeclaration(ArrowFunction/FunctionExpression)\n * からパラメータを抽出し、Definition配列として返す。\n *\n * @param node - パラメータを抽出する対象のノード\n * @returns パラメータ定義の配列\n */\n protected getParameterDefinitions(node: Node): Definition[] {\n const params = this.extractParameterDeclarations(node);\n return params.map((param, index) => ({\n name: param.getName(),\n index,\n required: !param.hasQuestionToken() && !param.hasInitializer(),\n }));\n }\n\n /**\n * ノードからParameterDeclarationの配列を抽出する\n *\n * 以下のノードタイプに対応:\n * - FunctionDeclaration: 直接パラメータを取得\n * - MethodDeclaration: 直接パラメータを取得\n * - VariableDeclaration: 初期化子がArrowFunctionまたはFunctionExpressionの場合にパラメータを取得\n *\n * @param node - パラメータを抽出する対象のノード\n * @returns ParameterDeclarationの配列\n */\n protected extractParameterDeclarations(node: Node): ParameterDeclaration[] {\n if (Node.isFunctionDeclaration(node)) {\n return node.getParameters();\n }\n\n if (Node.isMethodDeclaration(node)) {\n return node.getParameters();\n }\n\n if (Node.isVariableDeclaration(node)) {\n const initializer = node.getInitializer();\n if (initializer) {\n if (\n Node.isArrowFunction(initializer) ||\n Node.isFunctionExpression(initializer)\n ) {\n return initializer.getParameters();\n }\n }\n }\n\n return [];\n }\n\n /**\n * メイン分析処理\n *\n * @param classifiedDeclarations - 事前分類済みの宣言配列\n */\n analyze(classifiedDeclarations: ClassifiedDeclaration[]): AnalysisResult {\n // 1. 分析対象を収集\n const declarations = this.collect(classifiedDeclarations);\n\n // 2. 使用状況をレジストリに登録\n const usageRegistry = UsageRegistry.fromDeclarations(declarations);\n\n // 3. 常に同じ値が渡されているパラメータを抽出\n const constantParams = this.extractConstantParams(usageRegistry);\n\n // 4. 結果を構築\n return { constantParams, declarations };\n }\n\n /**\n * 分析対象を収集する(サブクラスで実装)\n *\n * @param classifiedDeclarations - 事前分類済みの宣言配列\n */\n protected abstract collect(\n classifiedDeclarations: ClassifiedDeclaration[],\n ): AnalyzedDeclarations;\n\n /**\n * 常に同じ値が渡されているパラメータを抽出\n */\n private extractConstantParams(usageRegistry: UsageRegistry): ConstantParams {\n const constantParams = new ConstantParams();\n\n for (const entry of usageRegistry.findAllConstantParams(this.minUsages)) {\n const {\n sourceFile,\n declarationName,\n declarationLine,\n paramName,\n candidate,\n } = entry;\n const value = candidate.representativeValue;\n\n // 以下の値種別は定数として報告しない(デフォルト値化の候補ではない)\n // - FunctionArgValue: 関数型の値(onClickに同じハンドラを渡している等)\n // - ParamRefArgValue: パラメータ参照(パラメータをそのまま次の関数に渡すパススルーパターン)\n // - MethodCallLiteralArgValue: メソッド呼び出し結果(実行時に異なる値を返す可能性がある)\n if (\n value instanceof FunctionArgValue ||\n value instanceof ParamRefArgValue ||\n value instanceof MethodCallLiteralArgValue\n ) {\n continue;\n }\n\n // 値種別によるフィルタリング\n if (!matchesValueTypes(value, this.allowedValueTypes)) {\n continue;\n }\n\n constantParams.push({\n declarationName,\n declarationSourceFile: sourceFile,\n declarationLine,\n paramName,\n value,\n usages: candidate.usages,\n });\n }\n\n return constantParams;\n }\n}\n","import { Node } from \"ts-morph\";\nimport {\n type AnalyzedDeclaration,\n AnalyzedDeclarations,\n} from \"@/domain/analyzedDeclarations\";\nimport type { AnalyzerOptions } from \"@/domain/analyzerOptions\";\nimport type { ClassifiedDeclaration } from \"@/domain/classifiedDeclaration\";\nimport { UsagesByParam } from \"@/domain/usagesByParam\";\nimport { ExtractUsages } from \"@/extraction/extractUsages\";\nimport { BaseAnalyzer } from \"./baseAnalyzer\";\n\n/**\n * クラスメソッドの引数分析を行うAnalyzer\n *\n * exportされたクラスのメソッド(static/instance)を収集し、\n * 各メソッドの引数使用状況を分析する。\n * 常に同じ値が渡されている引数を検出し、定数として報告する。\n *\n * @example\n * ```ts\n * const analyzer = new ClassMethodAnalyzer({ minUsages: 2 });\n * const result = analyzer.analyze(declarations);\n * console.log(result.constants);\n * ```\n */\nexport class ClassMethodAnalyzer extends BaseAnalyzer {\n constructor(options: AnalyzerOptions = {}) {\n super(options);\n }\n\n /**\n * 事前分類済みの宣言からクラスメソッドを収集する\n *\n * @param classifiedDeclarations - 事前分類済みの宣言配列(type: \"class\")\n * @returns 分析対象のメソッドとその使用状況(名前は「ClassName.methodName」形式)\n */\n protected collect(\n classifiedDeclarations: ClassifiedDeclaration[],\n ): AnalyzedDeclarations {\n const analyzedDeclarations = new AnalyzedDeclarations();\n\n for (const classifiedDeclaration of classifiedDeclarations) {\n const { exportName, sourceFile, declaration } = classifiedDeclaration;\n\n if (!Node.isClassDeclaration(declaration)) {\n continue;\n }\n\n const methods = declaration.getMethods();\n\n for (const method of methods) {\n const methodName = method.getName();\n const parameters = this.getParameterDefinitions(method);\n\n const usageGroup = new UsagesByParam();\n const analyzed: AnalyzedDeclaration = {\n name: `${exportName}.${methodName}`,\n sourceFilePath: sourceFile.getFilePath(),\n sourceLine: method.getStartLineNumber(),\n definitions: parameters,\n declaration: method,\n usages: usageGroup,\n };\n\n // メソッド名から参照を検索\n const nameNode = method.getNameNode();\n if (!Node.isIdentifier(nameNode)) {\n continue;\n }\n\n // 名前ノードから全参照を検索し、除外対象ファイルからの参照をフィルタ\n const references = this.findFilteredReferences(nameNode);\n\n // 参照からメソッド呼び出しを抽出し、usagesをパラメータ名ごとにグループ化\n for (const reference of references) {\n const refNode = reference.getNode();\n\n // obj.method の形でPropertyAccessExpressionの一部かチェック\n const propertyAccess = refNode.getParent();\n if (\n !propertyAccess ||\n !Node.isPropertyAccessExpression(propertyAccess)\n ) {\n continue;\n }\n\n // obj.method(...) の形でCallExpressionかチェック\n const callExpression = propertyAccess.getParent();\n if (!callExpression || !Node.isCallExpression(callExpression)) {\n continue;\n }\n\n // 呼び出し対象がPropertyAccessExpressionと一致するか確認\n if (callExpression.getExpression() !== propertyAccess) {\n continue;\n }\n\n // メソッド呼び出しから引数使用状況を抽出\n const usages = ExtractUsages.fromCall(\n callExpression,\n analyzed,\n this.getExpressionResolver(),\n );\n usageGroup.addAll(usages);\n }\n\n analyzedDeclarations.push(analyzed);\n }\n }\n\n return analyzedDeclarations;\n }\n}\n","import { Node, SyntaxKind } from \"ts-morph\";\nimport {\n type AnalyzedDeclaration,\n AnalyzedDeclarations,\n} from \"@/domain/analyzedDeclarations\";\nimport type { AnalyzerOptions } from \"@/domain/analyzerOptions\";\nimport type { ClassifiedDeclaration } from \"@/domain/classifiedDeclaration\";\nimport { UsagesByParam } from \"@/domain/usagesByParam\";\nimport { ExtractUsages } from \"@/extraction/extractUsages\";\nimport { BaseAnalyzer } from \"./baseAnalyzer\";\n\n/**\n * 関数の引数分析を行うAnalyzer\n *\n * exportされた関数を収集し、各関数の引数使用状況を分析する。\n * 常に同じ値が渡されている引数を検出し、定数として報告する。\n *\n * @example\n * ```ts\n * const analyzer = new FunctionAnalyzer({ minUsages: 2 });\n * const result = analyzer.analyze(declarations);\n * console.log(result.constants);\n * ```\n */\nexport class FunctionAnalyzer extends BaseAnalyzer {\n constructor(options: AnalyzerOptions = {}) {\n super(options);\n }\n\n /**\n * 事前分類済みの宣言から関数を収集する\n *\n * @param classifiedDeclarations - 事前分類済みの宣言配列(type: \"function\")\n * @returns 分析対象の関数とその使用状況\n */\n protected collect(\n classifiedDeclarations: ClassifiedDeclaration[],\n ): AnalyzedDeclarations {\n const analyzedDeclarations = new AnalyzedDeclarations();\n\n for (const classifiedDeclaration of classifiedDeclarations) {\n const { exportName, sourceFile, declaration } = classifiedDeclaration;\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 = this.findFilteredReferences(nameNode);\n\n // 関数の宣言からパラメータ定義を取得\n const parameters = this.getParameterDefinitions(declaration);\n\n const usageGroup = new UsagesByParam();\n const analyzed: AnalyzedDeclaration = {\n name: exportName,\n sourceFilePath: sourceFile.getFilePath(),\n sourceLine: declaration.getStartLineNumber(),\n definitions: parameters,\n declaration,\n usages: usageGroup,\n };\n\n // 参照から関数呼び出しを抽出し、usagesをパラメータ名ごとにグループ化\n for (const reference of references) {\n const refNode = reference.getNode();\n const parent = refNode.getParent();\n if (!parent) {\n continue;\n }\n\n // func(...) の形で関数呼び出しとして使われているかチェック\n const callExpression = parent.asKind(SyntaxKind.CallExpression);\n if (!callExpression) {\n continue;\n }\n\n // 呼び出し対象が参照ノードと一致するか確認\n const expression = callExpression.getExpression();\n if (expression !== refNode) {\n continue;\n }\n\n // 関数呼び出しから引数使用状況を抽出\n const usages = ExtractUsages.fromCall(\n callExpression,\n analyzed,\n this.getExpressionResolver(),\n );\n usageGroup.addAll(usages);\n }\n\n analyzedDeclarations.push(analyzed);\n }\n\n return analyzedDeclarations;\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 type {\n ClassifiedDeclaration,\n DeclarationType,\n} from \"@/domain/classifiedDeclaration\";\nimport { isReactComponent } from \"@/react/isReactComponent\";\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 classifiedDeclarations: ClassifiedDeclaration[] = [];\n\n for (const sourceFile of sourceFiles) {\n const exportedDecls = sourceFile.getExportedDeclarations();\n\n for (const [exportName, declarations] of exportedDecls) {\n // 関数宣言または変数宣言(アロー関数/関数式)を見つける\n const functionLikeDeclaration = declarations.find((declaration) =>\n isFunctionLike(declaration),\n );\n\n if (functionLikeDeclaration) {\n if (\n Node.isFunctionDeclaration(functionLikeDeclaration) ||\n Node.isVariableDeclaration(functionLikeDeclaration)\n ) {\n const type: DeclarationType = isReactComponent(\n functionLikeDeclaration,\n )\n ? \"react\"\n : \"function\";\n classifiedDeclarations.push({\n exportName,\n sourceFile,\n declaration: functionLikeDeclaration,\n type,\n });\n }\n continue;\n }\n\n // クラス宣言を見つける\n const classDeclaration = declarations.find((declaration) =>\n Node.isClassDeclaration(declaration),\n );\n\n if (classDeclaration && Node.isClassDeclaration(classDeclaration)) {\n classifiedDeclarations.push({\n exportName,\n sourceFile,\n declaration: classDeclaration,\n type: \"class\",\n });\n }\n }\n }\n\n return classifiedDeclarations;\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 { ClassMethodAnalyzer } from \"@/analyzer/classMethodAnalyzer\";\nimport { FunctionAnalyzer } from \"@/analyzer/functionAnalyzer\";\nimport type { AnalysisResult } from \"@/domain/analysisResult\";\nimport { AnalyzedDeclarations } from \"@/domain/analyzedDeclarations\";\nimport type { FileFilter } from \"@/domain/analyzerOptions\";\nimport type { CallSiteMap } from \"@/domain/callSiteMap\";\nimport { ConstantParams } from \"@/domain/constantParams\";\nimport type { ValueType } from \"@/extraction/valueTypeDetector\";\nimport { classifyDeclarations } from \"@/source/classifyDeclarations\";\nimport { isTestOrStorybookFile } from \"@/source/fileFilters\";\n\ninterface AnalyzeFunctionsOptions {\n shouldExcludeFile?: FileFilter;\n minUsages?: number;\n /** 検出対象の値種別。デフォルト: \"all\" */\n allowedValueTypes?: ValueType[] | \"all\";\n /** 呼び出し情報(パラメータ経由で渡された値を解決するために使用) */\n callSiteMap: CallSiteMap;\n}\n\n/**\n * 関数・クラスメソッドの引数使用状況を解析し、常に同じ値が渡されている引数を検出する\n *\n * @param sourceFiles - 解析対象のソースファイル配列\n * @param options - オプション設定\n * @returns 解析結果(定数引数、統計情報、exportされた関数・メソッド)\n *\n * @example\n * const project = new Project();\n * project.addSourceFilesAtPaths(\"src/**\\/*.ts\");\n * const result = analyzeFunctionsCore(project.getSourceFiles());\n */\nexport function analyzeFunctionsCore(\n sourceFiles: SourceFile[],\n options: AnalyzeFunctionsOptions,\n): AnalysisResult {\n const {\n shouldExcludeFile = isTestOrStorybookFile,\n minUsages = 2,\n allowedValueTypes = \"all\",\n callSiteMap,\n } = options;\n\n // 宣言を事前分類\n const declarations = classifyDeclarations(sourceFiles);\n const functions = declarations.filter((decl) => decl.type === \"function\");\n const classes = declarations.filter((decl) => decl.type === \"class\");\n\n const analyzerOptions = { shouldExcludeFile, minUsages, allowedValueTypes };\n\n // 関数を分析\n const functionAnalyzer = new FunctionAnalyzer(analyzerOptions);\n functionAnalyzer.setCallSiteMap(callSiteMap);\n const functionResult = functionAnalyzer.analyze(functions);\n\n // クラスメソッドを分析\n const classMethodAnalyzer = new ClassMethodAnalyzer(analyzerOptions);\n classMethodAnalyzer.setCallSiteMap(callSiteMap);\n const classMethodResult = classMethodAnalyzer.analyze(classes);\n\n // 結果をマージ\n return {\n constantParams: ConstantParams.merge(\n functionResult.constantParams,\n classMethodResult.constantParams,\n ),\n declarations: AnalyzedDeclarations.merge(\n functionResult.declarations,\n classMethodResult.declarations,\n ),\n };\n}\n","import { Node, type Type } from \"ts-morph\";\nimport type { Definition } from \"@/domain/usagesByParam\";\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 declaration of declarations) {\n if (\n Node.isPropertySignature(declaration) &&\n declaration.hasQuestionToken()\n ) {\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, SyntaxKind } from \"ts-morph\";\nimport {\n type AnalyzedDeclaration,\n AnalyzedDeclarations,\n} from \"@/domain/analyzedDeclarations\";\nimport type { AnalyzerOptions } from \"@/domain/analyzerOptions\";\nimport type { ClassifiedDeclaration } from \"@/domain/classifiedDeclaration\";\nimport { UsagesByParam } from \"@/domain/usagesByParam\";\nimport { ExtractUsages } from \"@/extraction/extractUsages\";\nimport { getProps } from \"@/react/getProps\";\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 classifiedDeclarations - 事前分類済みの宣言配列\n * @returns 分析対象のコンポーネントとその使用状況\n */\n protected collect(\n classifiedDeclarations: ClassifiedDeclaration[],\n ): AnalyzedDeclarations {\n const analyzedDeclarations = new AnalyzedDeclarations();\n\n for (const classifiedDeclaration of classifiedDeclarations) {\n const { exportName, sourceFile, declaration } = classifiedDeclaration;\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 = this.findFilteredReferences(nameNode);\n\n // コンポーネントの宣言からprops定義を取得\n const props = getProps(declaration);\n\n const usageGroup = new UsagesByParam();\n const analyzed: AnalyzedDeclaration = {\n name: exportName,\n sourceFilePath: sourceFile.getFilePath(),\n sourceLine: declaration.getStartLineNumber(),\n definitions: props,\n declaration,\n usages: usageGroup,\n };\n\n // 参照からJSX要素を抽出し、usagesをprop名ごとにグループ化\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 analyzed.definitions,\n this.getExpressionResolver(),\n );\n usageGroup.addAll(usages);\n }\n\n analyzedDeclarations.push(analyzed);\n }\n\n return analyzedDeclarations;\n }\n}\n","import type { SourceFile } from \"ts-morph\";\nimport { ComponentAnalyzer } from \"@/analyzer/componentAnalyzer\";\nimport type { AnalysisResult } from \"@/domain/analysisResult\";\nimport type { FileFilter } from \"@/domain/analyzerOptions\";\nimport type { CallSiteMap } from \"@/domain/callSiteMap\";\nimport type { ValueType } from \"@/extraction/valueTypeDetector\";\nimport { classifyDeclarations } from \"@/source/classifyDeclarations\";\nimport { isTestOrStorybookFile } from \"@/source/fileFilters\";\n\ninterface AnalyzePropsOptions {\n shouldExcludeFile?: FileFilter;\n minUsages?: number;\n /** 検出対象の値種別。デフォルト: \"all\" */\n allowedValueTypes?: ValueType[] | \"all\";\n /** 呼び出し情報(パラメータ経由で渡された値を解決するために使用) */\n callSiteMap: CallSiteMap;\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 {\n shouldExcludeFile = isTestOrStorybookFile,\n minUsages = 2,\n allowedValueTypes = \"all\",\n callSiteMap,\n } = 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 allowedValueTypes,\n });\n analyzer.setCallSiteMap(callSiteMap);\n\n return analyzer.analyze(components);\n}\n","import type { ArgValue } from \"./argValueClasses\";\n\n/**\n * 呼び出し箇所での引数情報\n */\nexport interface CallSiteArg {\n /** 引数のインデックス(0始まり)またはプロパティ名 */\n name: string;\n /** 引数の値 */\n value: ArgValue;\n /** 呼び出し元ファイルパス */\n filePath: string;\n /** 呼び出し元行番号 */\n line: number;\n}\n\n/**\n * 関数/コンポーネントへの呼び出し情報を管理するクラス\n * key: パラメータ名, value: 渡された値の配列\n */\nexport class CallSiteInfo extends Map<string, CallSiteArg[]> {\n /**\n * 引数情報を追加する\n * 既存の配列がある場合は追加、ない場合は新規作成\n */\n addArg(name: string, arg: CallSiteArg): void {\n const args = this.get(name) ?? [];\n args.push(arg);\n this.set(name, args);\n }\n}\n","import { type ArgValue, ParamRefArgValue } from \"./argValueClasses\";\nimport { type CallSiteArg, CallSiteInfo } from \"./callSiteInfo\";\n\n/**\n * すべての関数/コンポーネントの呼び出し情報を管理するクラス\n */\nexport class CallSiteMap extends Map<string, CallSiteInfo> {\n /**\n * 引数情報をCallSiteInfoに追加する\n */\n addArg(targetId: string, arg: CallSiteArg): void {\n const info = this.getOrCreateInfo(targetId);\n info.addArg(arg.name, arg);\n }\n\n /**\n * パラメータ参照を解決してArgValueを返す\n * callSiteMapを使ってパラメータに渡されたすべての値を取得し、\n * すべて同じ値ならその値を返す。解決できない場合は元のParamRefArgValueを返す。\n */\n resolveParamRef(paramRef: ParamRefArgValue): ArgValue {\n const resolved = this.resolveParameterValueInternal(paramRef);\n if (resolved !== undefined) {\n return resolved;\n }\n // 解決できない場合は元のParamRefArgValueを返す\n return paramRef;\n }\n\n /**\n * パラメータ参照を解決する(内部メソッド)\n * callSiteMapを使って、パラメータに渡されたすべての値を取得し、\n * すべて同じ値ならその値を返す。異なる値があればundefinedを返す。\n */\n private resolveParameterValueInternal(\n paramRef: ParamRefArgValue,\n visited: Set<string> = new Set(),\n ): ArgValue | undefined {\n // 循環参照を防ぐ\n const key = paramRef.toKey();\n if (visited.has(key)) {\n return undefined;\n }\n visited.add(key);\n\n const { filePath, functionName, path } = paramRef;\n const targetId = `${filePath}:${functionName}`;\n const callSiteInfo = this.get(targetId);\n\n if (!callSiteInfo) {\n return undefined;\n }\n\n // パラメータパスからプロパティ名を抽出\n // 例: \"props.number\" → \"number\", \"a\" → \"a\"\n const paramParts = path.split(\".\");\n // JSXの場合は props.xxx 形式なので最後のプロパティ名を使用\n // 通常関数の場合は最初の名前がそのまま引数名\n const propName =\n paramParts.length > 1 ? paramParts[paramParts.length - 1] : paramParts[0];\n\n const args = callSiteInfo.get(propName);\n if (!args || args.length === 0) {\n return undefined;\n }\n\n // すべての呼び出し箇所で渡された値を収集\n const resolvedKeys = new Set<string>();\n let resolvedValue: ArgValue | undefined;\n\n for (const arg of args) {\n // 再帰的にパラメータ参照を解決\n let resolved: ArgValue | undefined;\n if (arg.value instanceof ParamRefArgValue) {\n resolved = this.resolveParameterValueInternal(\n arg.value,\n new Set(visited),\n );\n } else {\n resolved = arg.value;\n }\n\n if (resolved === undefined) {\n return undefined;\n }\n\n const resolvedKey = resolved.toKey();\n resolvedKeys.add(resolvedKey);\n resolvedValue = resolved;\n }\n\n // すべて同じ値なら、その値を返す\n if (resolvedKeys.size === 1) {\n return resolvedValue;\n }\n\n // 異なる値がある場合は解決不可\n return undefined;\n }\n\n /**\n * targetIdに対応する呼び出し情報を取得する\n * 存在しない場合は新規作成して登録する\n */\n private getOrCreateInfo(targetId: string): CallSiteInfo {\n let callSiteInfo = this.get(targetId);\n if (!callSiteInfo) {\n callSiteInfo = new CallSiteInfo();\n this.set(targetId, callSiteInfo);\n }\n return callSiteInfo;\n }\n}\n","import {\n type CallExpression,\n type Identifier,\n type JsxOpeningElement,\n type JsxSelfClosingElement,\n Node,\n type SourceFile,\n SyntaxKind,\n} from \"ts-morph\";\nimport type { FileFilter } from \"@/domain/analyzerOptions\";\nimport { CallSiteMap } from \"@/domain/callSiteMap\";\nimport { isTestOrStorybookFile } from \"@/source/fileFilters\";\nimport { extractArgValue } from \"./extractArgValue\";\n\n/**\n * ソースファイルから呼び出し情報を収集する\n */\nexport class CallSiteCollector {\n private shouldExcludeFile: FileFilter;\n private callSiteMap: CallSiteMap;\n\n constructor(shouldExcludeFile: FileFilter = isTestOrStorybookFile) {\n this.shouldExcludeFile = shouldExcludeFile;\n this.callSiteMap = new CallSiteMap();\n }\n\n /**\n * ソースファイルからすべての呼び出し情報を収集する\n */\n collect(sourceFiles: SourceFile[]): CallSiteMap {\n for (const sourceFile of sourceFiles) {\n if (this.shouldExcludeFile(sourceFile.getFilePath())) continue;\n\n // JSX要素を収集\n for (const jsxElement of sourceFile.getDescendantsOfKind(\n SyntaxKind.JsxOpeningElement,\n )) {\n this.extractFromJsxElement(jsxElement);\n }\n for (const jsxElement of sourceFile.getDescendantsOfKind(\n SyntaxKind.JsxSelfClosingElement,\n )) {\n this.extractFromJsxElement(jsxElement);\n }\n\n // 関数呼び出しを収集\n for (const callExpression of sourceFile.getDescendantsOfKind(\n SyntaxKind.CallExpression,\n )) {\n this.extractFromCallExpression(callExpression);\n }\n }\n\n return this.callSiteMap;\n }\n\n /**\n * JSX要素から呼び出し情報を抽出して登録する\n * タグ名がIdentifierでない場合や、宣言が解決できない場合は何もしない\n */\n private extractFromJsxElement(\n jsxElement: JsxOpeningElement | JsxSelfClosingElement,\n ): void {\n const tagName = jsxElement.getTagNameNode();\n // JSXタグ名は Identifier(例: <Button />)または\n // PropertyAccessExpression(例: <Icons.Home />)のどちらか。\n // ここでは単純な識別子のみを対象とし、名前空間付きはスキップする\n if (!Node.isIdentifier(tagName)) return;\n\n const declaration = this.getDeclaration(tagName);\n if (!declaration) return;\n\n const targetId = this.createTargetId(declaration, tagName.getText());\n const filePath = jsxElement.getSourceFile().getFilePath();\n const line = jsxElement.getStartLineNumber();\n\n const attributesWithName = this.getAttributesWithName(jsxElement);\n\n for (const { name, node } of attributesWithName) {\n this.callSiteMap.addArg(targetId, {\n name,\n value: extractArgValue(node),\n filePath,\n line,\n });\n }\n }\n\n /**\n * 関数呼び出しから呼び出し情報を抽出して登録する\n * 呼び出し式がIdentifierでない場合や、宣言が解決できない場合は何もしない\n */\n private extractFromCallExpression(callExpression: CallExpression): void {\n const calleeExpression = callExpression.getExpression();\n if (!Node.isIdentifier(calleeExpression)) return;\n\n const declaration = this.getDeclaration(calleeExpression);\n if (!declaration) return;\n\n const targetId = this.createTargetId(\n declaration,\n calleeExpression.getText(),\n );\n const filePath = callExpression.getSourceFile().getFilePath();\n const line = callExpression.getStartLineNumber();\n\n const argsWithName = this.getArgsWithName(\n declaration,\n callExpression.getArguments(),\n );\n\n for (const { name, node } of argsWithName) {\n this.callSiteMap.addArg(targetId, {\n name,\n value: extractArgValue(node),\n filePath,\n line,\n });\n }\n }\n\n /**\n * 宣言からパラメータ名と引数ノードのペア配列を取得する\n * 関数宣言、アロー関数、関数式に対応\n * 引数が省略されている場合、node は undefined になる\n */\n private getArgsWithName(\n declaration: Node,\n args: Node[],\n ): { name: string; node: Node | undefined }[] {\n let paramNames: string[] = [];\n\n if (Node.isFunctionDeclaration(declaration)) {\n paramNames = declaration.getParameters().map((p) => p.getName());\n } else if (Node.isVariableDeclaration(declaration)) {\n const initializer = declaration.getInitializer();\n if (\n initializer &&\n (Node.isArrowFunction(initializer) ||\n Node.isFunctionExpression(initializer))\n ) {\n paramNames = initializer.getParameters().map((p) => p.getName());\n }\n }\n\n return paramNames.map((name, i) => ({ name, node: args[i] }));\n }\n\n /**\n * JSX要素から属性名とノードのペア配列を取得する\n */\n private getAttributesWithName(\n jsxElement: JsxOpeningElement | JsxSelfClosingElement,\n ): { name: string; node: Node }[] {\n return jsxElement\n .getAttributes()\n .filter(Node.isJsxAttribute)\n .map((attr) => ({ name: attr.getNameNode().getText(), node: attr }));\n }\n\n /**\n * 宣言ノードと名前からtargetIdを生成する\n * 形式: \"{ファイルパス}:{名前}\"\n */\n private createTargetId(declaration: Node, name: string): string {\n return `${declaration.getSourceFile().getFilePath()}:${name}`;\n }\n\n /**\n * 識別子から定義元の宣言ノードを取得する\n * インポートを通じて実際の定義を解決し、宣言ノードを返す\n */\n private getDeclaration(identifier: Identifier): Node | undefined {\n const symbol = identifier.getSymbol();\n const resolvedSymbol = symbol?.getAliasedSymbol() ?? symbol;\n return resolvedSymbol?.getDeclarations()[0];\n }\n}\n"],"mappings":"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"}
package/dist/cli.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { i as isTestOrStorybookFile, n as analyzeFunctionsCore, r as collectCallSites, t as analyzePropsCore } from "./analyzeProps-B0TjCZhP.mjs";
2
+ import { a as isTestOrStorybookFile, c as AnalyzedDeclarations, i as analyzeFunctionsCore, o as VALID_VALUE_TYPES, r as analyzePropsCore, s as ConstantParams, t as CallSiteCollector } from "./callSiteCollector-CGFPm0in.mjs";
3
3
  import { Project } from "ts-morph";
4
4
  import path from "node:path";
5
5
  import fs from "node:fs";
@@ -93,6 +93,14 @@ function validateConfig(config) {
93
93
  if (typeof config.targetDir !== "string" || config.targetDir === "") throw new Error(`Invalid config: targetDir must be a non-empty string, got ${config.targetDir}`);
94
94
  result.targetDir = config.targetDir;
95
95
  }
96
+ if ("valueTypes" in config) {
97
+ const value = config.valueTypes;
98
+ if (value === "all") result.allowedValueTypes = "all";
99
+ else if (Array.isArray(value)) {
100
+ for (const type of value) if (!VALID_VALUE_TYPES.includes(type)) throw new Error(`Invalid config: valueTypes contains invalid type "${type}" (valid values: ${VALID_VALUE_TYPES.join(", ")}, all)`);
101
+ result.allowedValueTypes = value;
102
+ } else throw new Error(`Invalid config: valueTypes must be "all" or an array of value types, got ${value}`);
103
+ }
96
104
  return result;
97
105
  }
98
106
 
@@ -104,7 +112,8 @@ const DEFAULT_OPTIONS = {
104
112
  minUsages: 2,
105
113
  target: "all",
106
114
  output: "simple",
107
- tsconfig: "./tsconfig.json"
115
+ tsconfig: "./tsconfig.json",
116
+ allowedValueTypes: "all"
108
117
  };
109
118
  var CliValidationError = class extends Error {
110
119
  constructor(message) {
@@ -124,6 +133,7 @@ const VALID_OPTIONS = [
124
133
  "--target",
125
134
  "--output",
126
135
  "--tsconfig",
136
+ "--value-types",
127
137
  "--help"
128
138
  ];
129
139
  /**
@@ -158,6 +168,13 @@ function parseCliOptions(args) {
158
168
  const value = arg.slice(11);
159
169
  if (value === "") throw new CliValidationError("Invalid value for --tsconfig: path cannot be empty");
160
170
  result.tsconfig = value;
171
+ } else if (arg.startsWith("--value-types=")) {
172
+ const value = arg.slice(14);
173
+ if (value === "") throw new CliValidationError("Invalid value for --value-types: value cannot be empty");
174
+ if (value === "all") continue;
175
+ const types = value.split(",").map((t) => t.trim());
176
+ for (const type of types) if (!VALID_VALUE_TYPES.includes(type)) throw new CliValidationError(`Invalid value type: "${type}" (valid values: ${VALID_VALUE_TYPES.join(", ")}, all)`);
177
+ result.allowedValueTypes = types;
161
178
  } else if (arg.startsWith("--")) {
162
179
  const optionName = arg.split("=")[0];
163
180
  if (!VALID_OPTIONS.includes(optionName)) throw new CliValidationError(`Unknown option: ${optionName}`);
@@ -194,6 +211,8 @@ Options:
194
211
  --target=<mode> Analysis target: all, components, functions (default: all)
195
212
  --output=<mode> Output mode: simple, verbose (default: simple)
196
213
  --tsconfig=<path> Path to tsconfig.json (default: ./tsconfig.json)
214
+ --value-types=<types> Value types to detect: boolean, number, string, enum, undefined, all (default: all)
215
+ Multiple types can be specified with comma: --value-types=boolean,string
197
216
  --help Show this help message
198
217
 
199
218
  Arguments:
@@ -210,50 +229,13 @@ function green(text) {
210
229
  return `\x1b[32m${text}\x1b[0m`;
211
230
  }
212
231
  /**
213
- * 値を表示用にフォーマットする
214
- *
215
- * 内部的にはenum区別のためにファイルパスを含むが、表示時は不要なので除去する
216
- * 例: "/path/to/file.ts:ButtonVariant.Primary=\"primary\"" → "ButtonVariant.Primary"
217
- */
218
- function formatValueForDisplay(value) {
219
- const enumMatch = value.match(/^.+:(\w+\.\w+)=.+$/);
220
- if (enumMatch) return enumMatch[1];
221
- return value;
222
- }
223
- /**
224
- * Constant[]を関数/コンポーネント単位でグループ化する
225
- */
226
- function groupConstantsByTarget(constants) {
227
- const groupMap = /* @__PURE__ */ new Map();
228
- for (const constant of constants) {
229
- const key = `${constant.targetSourceFile}:${constant.targetName}`;
230
- let group = groupMap.get(key);
231
- if (!group) {
232
- group = {
233
- targetName: constant.targetName,
234
- targetSourceFile: constant.targetSourceFile,
235
- targetLine: constant.targetLine,
236
- params: []
237
- };
238
- groupMap.set(key, group);
239
- }
240
- group.params.push({
241
- paramName: constant.paramName,
242
- value: constant.value,
243
- usageCount: constant.usages.length,
244
- usages: constant.usages
245
- });
246
- }
247
- return Array.from(groupMap.values());
248
- }
249
- /**
250
- * exportされた関数の一覧を出力
232
+ * 分析対象の一覧を出力
251
233
  */
252
- function printExportedFunctions(exported) {
234
+ function printDeclarations(declarations) {
253
235
  const lines = [
254
236
  "Collecting exported functions...",
255
- ` → Found ${exported.length} function(s)`,
256
- ...exported.map((fn) => ` - ${bold(green(fn.name))} (${path.relative(process.cwd(), fn.sourceFilePath)})`),
237
+ ` → Found ${declarations.length} function(s)`,
238
+ ...declarations.map((decl) => ` - ${bold(green(decl.name))} (${path.relative(process.cwd(), decl.sourceFilePath)})`),
257
239
  ""
258
240
  ];
259
241
  console.log(lines.join("\n"));
@@ -261,16 +243,16 @@ function printExportedFunctions(exported) {
261
243
  /**
262
244
  * 常に同じ値が渡されている引数を出力
263
245
  */
264
- function printConstantArguments(constants) {
265
- if (constants.length === 0) return;
266
- const grouped = groupConstantsByTarget(constants);
267
- for (const group of grouped) {
268
- const relativePath = path.relative(process.cwd(), group.targetSourceFile);
269
- const usageCount = group.params[0]?.usageCount ?? 0;
270
- const usages = group.params[0]?.usages ?? [];
271
- console.log(`${bold(green(group.targetName))} ${relativePath}:${group.targetLine}`);
246
+ function printConstantParams(constantParams) {
247
+ if (constantParams.isEmpty()) return;
248
+ const grouped = constantParams.groupByDeclaration();
249
+ for (const constantParamGroup of grouped) {
250
+ const relativePath = path.relative(process.cwd(), constantParamGroup.declarationSourceFile);
251
+ const usageCount = constantParamGroup.params[0]?.usageCount ?? 0;
252
+ const usages = constantParamGroup.params[0]?.usages ?? [];
253
+ console.log(`${bold(green(constantParamGroup.declarationName))} ${relativePath}:${constantParamGroup.declarationLine}`);
272
254
  console.log("Constant Arguments:");
273
- for (const param of group.params) console.log(` - ${param.paramName} = ${formatValueForDisplay(param.value)}`);
255
+ for (const param of constantParamGroup.params) console.log(` - ${param.paramName} = ${param.value.outputString()}`);
274
256
  console.log(`Usages (${usageCount}):`);
275
257
  for (const usage of usages) {
276
258
  const usagePath = path.relative(process.cwd(), usage.usageFilePath);
@@ -283,8 +265,8 @@ function printConstantArguments(constants) {
283
265
  * 統計情報を出力
284
266
  */
285
267
  function printStatistics(result) {
286
- const totalFunctions = result.exported.length;
287
- const functionsWithConstants = groupConstantsByTarget(result.constants).length;
268
+ const totalFunctions = result.declarations.length;
269
+ const functionsWithConstants = result.constantParams.groupByDeclaration().length;
288
270
  console.log("---");
289
271
  console.log(`Found ${functionsWithConstants} function(s) with constant arguments out of ${totalFunctions} function(s).`);
290
272
  }
@@ -292,9 +274,9 @@ function printStatistics(result) {
292
274
  * 解析結果を出力
293
275
  */
294
276
  function printAnalysisResult(result, mode) {
295
- if (mode === "verbose") printExportedFunctions(result.exported);
296
- if (result.constants.length === 0) console.log("No arguments with constant values were found.");
297
- else printConstantArguments(result.constants);
277
+ if (mode === "verbose") printDeclarations(result.declarations);
278
+ if (result.constantParams.isEmpty()) console.log("No arguments with constant values were found.");
279
+ else printConstantParams(result.constantParams);
298
280
  printStatistics(result);
299
281
  }
300
282
 
@@ -341,12 +323,13 @@ async function main() {
341
323
  if (error instanceof Error) exitWithError(error.message);
342
324
  throw error;
343
325
  }
344
- const { targetDir, minUsages, target, output, tsconfig } = {
326
+ const { targetDir, minUsages, target, output, tsconfig, allowedValueTypes } = {
345
327
  targetDir: path.resolve(cliOptions.targetDir ?? fileConfig.targetDir ?? DEFAULT_OPTIONS.targetDir),
346
328
  minUsages: cliOptions.minUsages ?? fileConfig.minUsages ?? DEFAULT_OPTIONS.minUsages,
347
329
  target: cliOptions.target ?? fileConfig.target ?? DEFAULT_OPTIONS.target,
348
330
  output: cliOptions.output ?? fileConfig.output ?? DEFAULT_OPTIONS.output,
349
- tsconfig: cliOptions.tsconfig ?? fileConfig.tsconfig ?? DEFAULT_OPTIONS.tsconfig
331
+ tsconfig: cliOptions.tsconfig ?? fileConfig.tsconfig ?? DEFAULT_OPTIONS.tsconfig,
332
+ allowedValueTypes: cliOptions.allowedValueTypes ?? fileConfig.allowedValueTypes ?? DEFAULT_OPTIONS.allowedValueTypes
350
333
  };
351
334
  try {
352
335
  validateTargetDir(targetDir);
@@ -366,28 +349,30 @@ async function main() {
366
349
  console.log(`Analysis target: ${target}\n`);
367
350
  }
368
351
  const sourceFilesToAnalyze = createFilteredSourceFiles(targetDir, { tsConfigFilePath: tsconfig });
369
- const callSiteMap = collectCallSites(sourceFilesToAnalyze);
370
- const allExported = [];
371
- const allConstants = [];
352
+ const callSiteMap = new CallSiteCollector().collect(sourceFilesToAnalyze);
353
+ const declarationsToMerge = [];
354
+ const constantParamsToMerge = [];
372
355
  if (target === "all" || target === "components") {
373
356
  const propsResult = analyzePropsCore(sourceFilesToAnalyze, {
374
357
  minUsages,
358
+ allowedValueTypes,
375
359
  callSiteMap
376
360
  });
377
- allExported.push(...propsResult.exported);
378
- allConstants.push(...propsResult.constants);
361
+ declarationsToMerge.push(propsResult.declarations);
362
+ constantParamsToMerge.push(propsResult.constantParams);
379
363
  }
380
364
  if (target === "all" || target === "functions") {
381
365
  const functionsResult = analyzeFunctionsCore(sourceFilesToAnalyze, {
382
366
  minUsages,
367
+ allowedValueTypes,
383
368
  callSiteMap
384
369
  });
385
- allExported.push(...functionsResult.exported);
386
- allConstants.push(...functionsResult.constants);
370
+ declarationsToMerge.push(functionsResult.declarations);
371
+ constantParamsToMerge.push(functionsResult.constantParams);
387
372
  }
388
373
  printAnalysisResult({
389
- exported: allExported,
390
- constants: allConstants
374
+ declarations: AnalyzedDeclarations.merge(...declarationsToMerge),
375
+ constantParams: ConstantParams.merge(...constantParamsToMerge)
391
376
  }, output);
392
377
  }
393
378
  main().catch((error) => {