dittory 0.0.3 → 0.0.4
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.
- package/dist/{analyzeProps-B0TjCZhP.mjs → analyzeProps-6k4QTasK.mjs} +182 -11
- package/dist/analyzeProps-6k4QTasK.mjs.map +1 -0
- package/dist/cli.mjs +26 -4
- package/dist/cli.mjs.map +1 -1
- package/dist/index.d.mts +11 -1
- package/dist/index.mjs +1 -1
- package/package.json +1 -1
- package/dist/analyzeProps-B0TjCZhP.mjs.map +0 -1
|
@@ -148,6 +148,18 @@ function createTargetId(filePath, name) {
|
|
|
148
148
|
return `${filePath}:${name}`;
|
|
149
149
|
}
|
|
150
150
|
/**
|
|
151
|
+
* 式がthisキーワードへのプロパティアクセスかどうかを判定する
|
|
152
|
+
* ネストしたアクセス(例: this.logger.name)にも対応
|
|
153
|
+
*/
|
|
154
|
+
function isThisPropertyAccess(expression) {
|
|
155
|
+
if (Node.isPropertyAccessExpression(expression)) {
|
|
156
|
+
const baseExpr = expression.getExpression();
|
|
157
|
+
if (baseExpr.getKind() === SyntaxKind.ThisKeyword) return true;
|
|
158
|
+
return isThisPropertyAccess(baseExpr);
|
|
159
|
+
}
|
|
160
|
+
return false;
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
151
163
|
* 式から ArgValue を抽出する
|
|
152
164
|
*
|
|
153
165
|
* 式の値を型安全な ArgValue として返す。
|
|
@@ -183,6 +195,14 @@ function extractArgValue(expression) {
|
|
|
183
195
|
}
|
|
184
196
|
}
|
|
185
197
|
if (isParameterReference(expression.getExpression())) return createParamRefValue(expression);
|
|
198
|
+
if (isThisPropertyAccess(expression)) {
|
|
199
|
+
const sourceFile = expression.getSourceFile();
|
|
200
|
+
const line = expression.getStartLineNumber();
|
|
201
|
+
return {
|
|
202
|
+
type: ArgValueType.Literal,
|
|
203
|
+
value: `[this]${sourceFile.getFilePath()}:${line}:${expression.getText()}`
|
|
204
|
+
};
|
|
205
|
+
}
|
|
186
206
|
}
|
|
187
207
|
if (Node.isIdentifier(expression)) {
|
|
188
208
|
if (expression.getText() === "undefined") return { type: ArgValueType.Undefined };
|
|
@@ -213,6 +233,17 @@ function extractArgValue(expression) {
|
|
|
213
233
|
type: ArgValueType.Literal,
|
|
214
234
|
value: type.getText()
|
|
215
235
|
};
|
|
236
|
+
if (Node.isCallExpression(expression)) {
|
|
237
|
+
const calleeExpr = expression.getExpression();
|
|
238
|
+
if (Node.isPropertyAccessExpression(calleeExpr)) {
|
|
239
|
+
const sourceFile = expression.getSourceFile();
|
|
240
|
+
const line = expression.getStartLineNumber();
|
|
241
|
+
return {
|
|
242
|
+
type: ArgValueType.Literal,
|
|
243
|
+
value: `[methodCall]${sourceFile.getFilePath()}:${line}:${expression.getText()}`
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
}
|
|
216
247
|
return {
|
|
217
248
|
type: ArgValueType.Literal,
|
|
218
249
|
value: expression.getText()
|
|
@@ -345,22 +376,26 @@ const FUNCTION_VALUE_PREFIX = "[function]";
|
|
|
345
376
|
* @param context - 呼び出し情報などのコンテキスト
|
|
346
377
|
*/
|
|
347
378
|
function resolveExpressionValue(expression, context) {
|
|
348
|
-
return argValueToString(extractArgValue(expression), context
|
|
379
|
+
return argValueToString(extractArgValue(expression), context, {
|
|
380
|
+
filePath: expression.getSourceFile().getFilePath(),
|
|
381
|
+
line: expression.getStartLineNumber()
|
|
382
|
+
});
|
|
349
383
|
}
|
|
350
384
|
/**
|
|
351
385
|
* ArgValue を文字列表現に変換する
|
|
352
386
|
*
|
|
353
387
|
* @param value - 変換対象の ArgValue
|
|
354
388
|
* @param context - パラメータ参照解決用のコンテキスト
|
|
389
|
+
* @param usageLocation - 使用箇所の位置情報(解決できないParamRefのユニーク化に使用)
|
|
355
390
|
*/
|
|
356
|
-
function argValueToString(value, context) {
|
|
391
|
+
function argValueToString(value, context, usageLocation) {
|
|
357
392
|
switch (value.type) {
|
|
358
393
|
case ArgValueType.Literal: return value.value;
|
|
359
394
|
case ArgValueType.Function: return `${FUNCTION_VALUE_PREFIX}${value.filePath}:${value.line}`;
|
|
360
395
|
case ArgValueType.ParamRef: {
|
|
361
396
|
const resolved = resolveParameterValue(value, context.callSiteMap);
|
|
362
397
|
if (resolved !== void 0) return argValueToKey(resolved);
|
|
363
|
-
return argValueToKey(value)
|
|
398
|
+
return `[paramRef]${usageLocation.filePath}:${usageLocation.line}:${argValueToKey(value)}`;
|
|
364
399
|
}
|
|
365
400
|
case ArgValueType.Undefined: return UNDEFINED_VALUE;
|
|
366
401
|
}
|
|
@@ -371,6 +406,9 @@ function argValueToString(value, context) {
|
|
|
371
406
|
/**
|
|
372
407
|
* オブジェクトリテラルを再帰的に解析し、フラットなkey-valueペアを返す
|
|
373
408
|
*
|
|
409
|
+
* 期待される型(コンテキスト型)から省略されたプロパティも検出し、
|
|
410
|
+
* [undefined] として記録する。
|
|
411
|
+
*
|
|
374
412
|
* @param expression - 解析対象の式ノード
|
|
375
413
|
* @param prefix - キー名のプレフィックス(ネストしたプロパティの親パスを表す)
|
|
376
414
|
* @param context - 呼び出し情報などのコンテキスト
|
|
@@ -384,6 +422,16 @@ function flattenObjectExpression(expression, prefix, context) {
|
|
|
384
422
|
key: prefix,
|
|
385
423
|
value: resolveExpressionValue(expression, context)
|
|
386
424
|
}];
|
|
425
|
+
const existingValues = flattenExistingProperties(expression, prefix, context);
|
|
426
|
+
const contextualType = expression.getContextualType();
|
|
427
|
+
const objectType = contextualType ? extractObjectType(contextualType) : null;
|
|
428
|
+
const missingValues = objectType ? getMissingProperties(expression, objectType, prefix) : [];
|
|
429
|
+
return [...existingValues, ...missingValues];
|
|
430
|
+
}
|
|
431
|
+
/**
|
|
432
|
+
* オブジェクトリテラル内の既存プロパティをフラット化する
|
|
433
|
+
*/
|
|
434
|
+
function flattenExistingProperties(expression, prefix, context) {
|
|
387
435
|
return expression.getProperties().flatMap((property) => {
|
|
388
436
|
if (Node.isPropertyAssignment(property)) {
|
|
389
437
|
const propertyName = property.getName();
|
|
@@ -401,6 +449,77 @@ function flattenObjectExpression(expression, prefix, context) {
|
|
|
401
449
|
return [];
|
|
402
450
|
});
|
|
403
451
|
}
|
|
452
|
+
/**
|
|
453
|
+
* 期待される型と比較して、省略されたプロパティを検出する
|
|
454
|
+
*
|
|
455
|
+
* 省略されたプロパティがオブジェクト型の場合、そのネストプロパティも
|
|
456
|
+
* 再帰的に [undefined] として出力する。これにより、親プロパティが
|
|
457
|
+
* 省略された場合でも、ネストプロパティが他の呼び出しと比較可能になる。
|
|
458
|
+
*
|
|
459
|
+
* @param objectExpression - オブジェクトリテラル
|
|
460
|
+
* @param expectedType - 期待される型(コンテキスト型)
|
|
461
|
+
* @param prefix - キー名のプレフィックス
|
|
462
|
+
* @returns 省略されたプロパティの配列(値は [undefined])
|
|
463
|
+
*/
|
|
464
|
+
function getMissingProperties(objectExpression, expectedType, prefix) {
|
|
465
|
+
const existingPropNames = /* @__PURE__ */ new Set();
|
|
466
|
+
for (const property of objectExpression.getProperties()) if (Node.isPropertyAssignment(property)) existingPropNames.add(property.getName());
|
|
467
|
+
else if (Node.isShorthandPropertyAssignment(property)) existingPropNames.add(property.getName());
|
|
468
|
+
const missingValues = [];
|
|
469
|
+
for (const propSymbol of expectedType.getProperties()) {
|
|
470
|
+
const propName = propSymbol.getName();
|
|
471
|
+
if (existingPropNames.has(propName)) continue;
|
|
472
|
+
const nestedPrefix = prefix ? `${prefix}.${propName}` : propName;
|
|
473
|
+
missingValues.push({
|
|
474
|
+
key: nestedPrefix,
|
|
475
|
+
value: UNDEFINED_VALUE
|
|
476
|
+
});
|
|
477
|
+
const propType = propSymbol.getValueDeclaration()?.getType();
|
|
478
|
+
if (propType) {
|
|
479
|
+
const objType = extractObjectType(propType);
|
|
480
|
+
if (objType) missingValues.push(...getNestedMissingProperties(objType, nestedPrefix));
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
return missingValues;
|
|
484
|
+
}
|
|
485
|
+
/**
|
|
486
|
+
* 省略された親プロパティのネストプロパティを再帰的に [undefined] として出力
|
|
487
|
+
*/
|
|
488
|
+
function getNestedMissingProperties(parentType, prefix) {
|
|
489
|
+
const result = [];
|
|
490
|
+
for (const propSymbol of parentType.getProperties()) {
|
|
491
|
+
const nestedPrefix = `${prefix}.${propSymbol.getName()}`;
|
|
492
|
+
result.push({
|
|
493
|
+
key: nestedPrefix,
|
|
494
|
+
value: UNDEFINED_VALUE
|
|
495
|
+
});
|
|
496
|
+
const propType = propSymbol.getValueDeclaration()?.getType();
|
|
497
|
+
if (propType) {
|
|
498
|
+
const objType = extractObjectType(propType);
|
|
499
|
+
if (objType) result.push(...getNestedMissingProperties(objType, nestedPrefix));
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
return result;
|
|
503
|
+
}
|
|
504
|
+
/**
|
|
505
|
+
* 型からオブジェクト部分を抽出する
|
|
506
|
+
* ユニオン型(例: `{ a: number } | undefined`)から `undefined` を除外して
|
|
507
|
+
* オブジェクト型部分を返す。オブジェクト型でない場合は null を返す。
|
|
508
|
+
*/
|
|
509
|
+
function extractObjectType(type) {
|
|
510
|
+
if (type.isUnion()) {
|
|
511
|
+
for (const unionType of type.getUnionTypes()) {
|
|
512
|
+
if (unionType.isUndefined() || unionType.isNull()) continue;
|
|
513
|
+
const objType = extractObjectType(unionType);
|
|
514
|
+
if (objType) return objType;
|
|
515
|
+
}
|
|
516
|
+
return null;
|
|
517
|
+
}
|
|
518
|
+
if (type.isString() || type.isNumber() || type.isBoolean() || type.isUndefined() || type.isNull() || type.isLiteral()) return null;
|
|
519
|
+
if (type.isArray()) return null;
|
|
520
|
+
if (type.isObject() && type.getProperties().length > 0) return type;
|
|
521
|
+
return null;
|
|
522
|
+
}
|
|
404
523
|
|
|
405
524
|
//#endregion
|
|
406
525
|
//#region src/extraction/hasDisableComment.ts
|
|
@@ -536,6 +655,51 @@ function getSingleValueFromSet(values) {
|
|
|
536
655
|
return firstValue;
|
|
537
656
|
}
|
|
538
657
|
|
|
658
|
+
//#endregion
|
|
659
|
+
//#region src/utils/valueTypeDetector.ts
|
|
660
|
+
/**
|
|
661
|
+
* 有効な値種別の一覧
|
|
662
|
+
*/
|
|
663
|
+
const VALID_VALUE_TYPES = [
|
|
664
|
+
"boolean",
|
|
665
|
+
"number",
|
|
666
|
+
"string",
|
|
667
|
+
"enum",
|
|
668
|
+
"undefined"
|
|
669
|
+
];
|
|
670
|
+
/**
|
|
671
|
+
* enum値の文字列表現パターン
|
|
672
|
+
* 形式: "filePath:EnumName.MemberName=value"
|
|
673
|
+
*/
|
|
674
|
+
const ENUM_PATTERN = /^.+:\w+\.\w+=.+$/;
|
|
675
|
+
/**
|
|
676
|
+
* 値の文字列表現から種別を判定する
|
|
677
|
+
*
|
|
678
|
+
* @param value - resolveExpressionValue で得られた値の文字列表現
|
|
679
|
+
* @returns 検出された種別、または判定不能な場合は null
|
|
680
|
+
*/
|
|
681
|
+
function detectValueType(value) {
|
|
682
|
+
if (value === "true" || value === "false") return "boolean";
|
|
683
|
+
if (value === UNDEFINED_VALUE) return "undefined";
|
|
684
|
+
if (ENUM_PATTERN.test(value)) return "enum";
|
|
685
|
+
if (value.startsWith("\"") && value.endsWith("\"")) return "string";
|
|
686
|
+
if (value !== "" && !Number.isNaN(Number(value))) return "number";
|
|
687
|
+
return null;
|
|
688
|
+
}
|
|
689
|
+
/**
|
|
690
|
+
* 値が指定された種別に含まれるか判定する
|
|
691
|
+
*
|
|
692
|
+
* @param value - resolveExpressionValue で得られた値の文字列表現
|
|
693
|
+
* @param allowedTypes - 許可する種別の配列、または "all"
|
|
694
|
+
* @returns 指定種別に含まれる場合は true
|
|
695
|
+
*/
|
|
696
|
+
function matchesValueTypes(value, allowedTypes) {
|
|
697
|
+
if (allowedTypes === "all") return true;
|
|
698
|
+
const detectedType = detectValueType(value);
|
|
699
|
+
if (detectedType === null) return false;
|
|
700
|
+
return allowedTypes.includes(detectedType);
|
|
701
|
+
}
|
|
702
|
+
|
|
539
703
|
//#endregion
|
|
540
704
|
//#region src/analyzer/baseAnalyzer.ts
|
|
541
705
|
/**
|
|
@@ -544,10 +708,12 @@ function getSingleValueFromSet(values) {
|
|
|
544
708
|
var BaseAnalyzer = class {
|
|
545
709
|
shouldExcludeFile;
|
|
546
710
|
minUsages;
|
|
711
|
+
valueTypes;
|
|
547
712
|
callSiteMap;
|
|
548
713
|
constructor(options = {}) {
|
|
549
714
|
this.shouldExcludeFile = options.shouldExcludeFile ?? isTestOrStorybookFile;
|
|
550
715
|
this.minUsages = options.minUsages ?? 2;
|
|
716
|
+
this.valueTypes = options.valueTypes ?? "all";
|
|
551
717
|
}
|
|
552
718
|
/**
|
|
553
719
|
* 呼び出し情報を設定する
|
|
@@ -656,9 +822,11 @@ var BaseAnalyzer = class {
|
|
|
656
822
|
usages
|
|
657
823
|
});
|
|
658
824
|
}
|
|
825
|
+
const totalCallCount = Math.max(...Object.values(item.usages).map((usages) => usages.length), 0);
|
|
659
826
|
fileMap.set(item.name, {
|
|
660
827
|
line: item.sourceLine,
|
|
661
|
-
params: paramMap
|
|
828
|
+
params: paramMap,
|
|
829
|
+
totalCallCount
|
|
662
830
|
});
|
|
663
831
|
}
|
|
664
832
|
return groupedMap;
|
|
@@ -669,9 +837,10 @@ var BaseAnalyzer = class {
|
|
|
669
837
|
extractConstants(groupedMap) {
|
|
670
838
|
const result = [];
|
|
671
839
|
for (const [sourceFile, targetMap] of groupedMap) for (const [targetName, targetInfo] of targetMap) for (const [paramName, usageData] of targetInfo.params) {
|
|
672
|
-
if (!(usageData.usages.length >= this.minUsages && usageData.values.size === 1)) continue;
|
|
840
|
+
if (!(usageData.usages.length >= this.minUsages && usageData.values.size === 1 && usageData.usages.length === targetInfo.totalCallCount)) continue;
|
|
673
841
|
const value = getSingleValueFromSet(usageData.values);
|
|
674
842
|
if (value.startsWith(FUNCTION_VALUE_PREFIX)) continue;
|
|
843
|
+
if (!matchesValueTypes(value, this.valueTypes)) continue;
|
|
675
844
|
result.push({
|
|
676
845
|
targetName,
|
|
677
846
|
targetSourceFile: sourceFile,
|
|
@@ -912,13 +1081,14 @@ function isFunctionLike(declaration) {
|
|
|
912
1081
|
* const result = analyzeFunctionsCore(project.getSourceFiles());
|
|
913
1082
|
*/
|
|
914
1083
|
function analyzeFunctionsCore(sourceFiles, options) {
|
|
915
|
-
const { shouldExcludeFile = isTestOrStorybookFile, minUsages = 2, callSiteMap } = options;
|
|
1084
|
+
const { shouldExcludeFile = isTestOrStorybookFile, minUsages = 2, valueTypes = "all", callSiteMap } = options;
|
|
916
1085
|
const declarations = classifyDeclarations(sourceFiles);
|
|
917
1086
|
const functions = declarations.filter((decl) => decl.type === "function");
|
|
918
1087
|
const classes = declarations.filter((decl) => decl.type === "class");
|
|
919
1088
|
const analyzerOptions = {
|
|
920
1089
|
shouldExcludeFile,
|
|
921
|
-
minUsages
|
|
1090
|
+
minUsages,
|
|
1091
|
+
valueTypes
|
|
922
1092
|
};
|
|
923
1093
|
const functionAnalyzer = new FunctionAnalyzer(analyzerOptions);
|
|
924
1094
|
functionAnalyzer.setCallSiteMap(callSiteMap);
|
|
@@ -1088,16 +1258,17 @@ var ComponentAnalyzer = class extends BaseAnalyzer {
|
|
|
1088
1258
|
* const result = analyzePropsCore(project.getSourceFiles());
|
|
1089
1259
|
*/
|
|
1090
1260
|
function analyzePropsCore(sourceFiles, options) {
|
|
1091
|
-
const { shouldExcludeFile = isTestOrStorybookFile, minUsages = 2, callSiteMap } = options;
|
|
1261
|
+
const { shouldExcludeFile = isTestOrStorybookFile, minUsages = 2, valueTypes = "all", callSiteMap } = options;
|
|
1092
1262
|
const components = classifyDeclarations(sourceFiles).filter((decl) => decl.type === "react");
|
|
1093
1263
|
const analyzer = new ComponentAnalyzer({
|
|
1094
1264
|
shouldExcludeFile,
|
|
1095
|
-
minUsages
|
|
1265
|
+
minUsages,
|
|
1266
|
+
valueTypes
|
|
1096
1267
|
});
|
|
1097
1268
|
analyzer.setCallSiteMap(callSiteMap);
|
|
1098
1269
|
return analyzer.analyze(components);
|
|
1099
1270
|
}
|
|
1100
1271
|
|
|
1101
1272
|
//#endregion
|
|
1102
|
-
export { isTestOrStorybookFile as i, analyzeFunctionsCore as n,
|
|
1103
|
-
//# sourceMappingURL=analyzeProps-
|
|
1273
|
+
export { isTestOrStorybookFile as a, collectCallSites as i, analyzeFunctionsCore as n, VALID_VALUE_TYPES as r, analyzePropsCore as t };
|
|
1274
|
+
//# sourceMappingURL=analyzeProps-6k4QTasK.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"analyzeProps-6k4QTasK.mjs","names":["current: Node | undefined","resolvedValue: ArgValue | undefined","value: ArgValue","callSiteMap: CallSiteMap","paramNames: string[]","missingValues: FlattenedValue[]","result: FlattenedValue[]","current: Node | undefined","usages: Usage[]","VALID_VALUE_TYPES: readonly ValueType[]","groupedMap: GroupedMap","result: Constant[]","results: Exported[]","callable: Exported","groupedUsages: Record<string, Usage[]>","results: Exported[]","callable: Exported","groupedUsages: Record<string, Usage[]>","results: ClassifiedDeclaration[]","type: DeclarationType","propsParam: Node | undefined","exportedComponents: Exported[]","component: Exported","groupedUsages: Record<string, Usage[]>"],"sources":["../src/source/fileFilters.ts","../src/extraction/parameterUtils.ts","../src/extraction/callSiteCollector.ts","../src/extraction/resolveExpressionValue.ts","../src/extraction/flattenObjectExpression.ts","../src/extraction/hasDisableComment.ts","../src/extraction/extractUsages.ts","../src/utils/getSingleValueFromSet.ts","../src/utils/valueTypeDetector.ts","../src/analyzer/baseAnalyzer.ts","../src/analyzer/classMethodAnalyzer.ts","../src/analyzer/functionAnalyzer.ts","../src/components/isReactComponent.ts","../src/source/classifyDeclarations.ts","../src/analyzeFunctions.ts","../src/components/getProps.ts","../src/analyzer/componentAnalyzer.ts","../src/analyzeProps.ts"],"sourcesContent":["/**\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 { Node, SyntaxKind } from \"ts-morph\";\nimport {\n type ArgValue,\n ArgValueType,\n type CallSiteMap,\n} from \"./callSiteCollector\";\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 decl = symbol?.getDeclarations()[0];\n if (!decl) return false;\n const kind = decl.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 */\nexport function 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 */\nexport function 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 */\nexport function 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 { type: ArgValueType.Literal, value: expression.getText() };\n }\n\n const functionName = getFunctionName(functionScope);\n const path = buildParameterPath(expression);\n\n return { type: ArgValueType.ParamRef, filePath, functionName, path };\n}\n\n/**\n * ArgValue を比較可能な文字列キーに変換\n * 同じ値かどうかの判定に使用\n */\nexport function argValueToKey(value: ArgValue): string {\n switch (value.type) {\n case ArgValueType.Literal:\n return `literal:${value.value}`;\n case ArgValueType.Function:\n return `function:${value.filePath}:${value.line}`;\n case ArgValueType.ParamRef:\n return `paramRef:${value.filePath}:${value.functionName}:${value.path}`;\n case ArgValueType.Undefined:\n return \"undefined\";\n }\n}\n\n/**\n * パラメータ参照を解決する\n * callSiteMapを使って、パラメータに渡されたすべての値を取得し、\n * すべて同じ値ならその値を返す。異なる値があればundefinedを返す。\n *\n * @param argValue - 解決対象の ArgValue\n * @param callSiteMap - 呼び出し情報マップ\n * @param visited - 循環参照防止用のセット\n * @returns 解決された ArgValue。異なる値がある場合や解決できない場合はundefined\n */\nexport function resolveParameterValue(\n argValue: ArgValue,\n callSiteMap: CallSiteMap,\n visited: Set<string> = new Set(),\n): ArgValue | undefined {\n // パラメータ参照でない場合はそのまま返す\n if (argValue.type !== ArgValueType.ParamRef) {\n return argValue;\n }\n\n // 循環参照を防ぐ\n const key = argValueToKey(argValue);\n if (visited.has(key)) {\n return undefined;\n }\n visited.add(key);\n\n const { filePath, functionName, path } = argValue;\n const targetId = `${filePath}:${functionName}`;\n const callSiteInfo = callSiteMap.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 const resolved = resolveParameterValue(\n arg.value,\n callSiteMap,\n new Set(visited),\n );\n if (resolved === undefined) {\n return undefined;\n }\n\n const resolvedKey = argValueToKey(resolved);\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","import {\n type CallExpression,\n type JsxOpeningElement,\n type JsxSelfClosingElement,\n Node,\n type SourceFile,\n SyntaxKind,\n} from \"ts-morph\";\nimport { isTestOrStorybookFile } from \"@/source/fileFilters\";\nimport type { FileFilter } from \"@/types\";\nimport { createParamRefValue, isParameterReference } from \"./parameterUtils\";\n\n/**\n * ArgValueのtype識別子\n */\nexport const ArgValueType = {\n Literal: \"literal\",\n Function: \"function\",\n ParamRef: \"paramRef\",\n Undefined: \"undefined\",\n} as const;\n\n/**\n * 引数の値を表す union 型\n * 文字列エンコーディングの代わりに型安全な表現を使用\n */\nexport type ArgValue =\n | { type: typeof ArgValueType.Literal; value: string }\n | { type: typeof ArgValueType.Function; filePath: string; line: number }\n | {\n type: typeof ArgValueType.ParamRef;\n filePath: string;\n functionName: string;\n path: string;\n }\n | { type: typeof ArgValueType.Undefined };\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 type CallSiteInfo = Map<string, CallSiteArg[]>;\n\n/**\n * すべての関数/コンポーネントの呼び出し情報\n * key: \"ファイルパス:関数名\" 形式の識別子\n */\nexport type CallSiteMap = Map<string, CallSiteInfo>;\n\n/**\n * 関数/コンポーネントの識別子を生成する\n */\nexport function createTargetId(filePath: string, name: string): string {\n return `${filePath}:${name}`;\n}\n\n/**\n * 識別子から関数/コンポーネント名を抽出する\n */\nexport function parseTargetId(targetId: string): {\n filePath: string;\n name: string;\n} {\n const lastColonIndex = targetId.lastIndexOf(\":\");\n return {\n filePath: targetId.substring(0, lastColonIndex),\n name: targetId.substring(lastColonIndex + 1),\n };\n}\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 - 解析対象の式ノード\n * @returns 式の値を表す ArgValue\n */\nexport function extractArgValue(expression: Node): ArgValue {\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 {\n type: ArgValueType.Function,\n filePath: sourceFile.getFilePath(),\n line,\n };\n }\n\n // PropertyAccessExpression (例: Status.Active, props.number)\n if (Node.isPropertyAccessExpression(expression)) {\n const symbol = expression.getSymbol();\n const decl = symbol?.getDeclarations()[0];\n\n // enum memberの場合\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 return {\n type: ArgValueType.Literal,\n value: `${filePath}:${enumName}.${memberName}=${JSON.stringify(value)}`,\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 const sourceFile = expression.getSourceFile();\n const line = expression.getStartLineNumber();\n return {\n type: ArgValueType.Literal,\n value: `[this]${sourceFile.getFilePath()}:${line}:${expression.getText()}`,\n };\n }\n }\n\n // Identifier (変数参照)\n if (Node.isIdentifier(expression)) {\n if (expression.getText() === \"undefined\") {\n return { type: ArgValueType.Undefined };\n }\n\n const symbol = expression.getSymbol();\n const resolvedSymbol = symbol?.getAliasedSymbol() ?? symbol;\n const decl = resolvedSymbol?.getDeclarations()[0];\n\n if (decl) {\n const kind = decl.getKind();\n // パラメータまたはBindingElementの場合\n if (kind === SyntaxKind.Parameter || kind === SyntaxKind.BindingElement) {\n return createParamRefValue(expression);\n }\n\n // 変数宣言の場合は初期化子を再帰的に解決\n if (Node.isVariableDeclaration(decl)) {\n const initializer = decl.getInitializer();\n if (initializer) {\n return extractArgValue(initializer);\n }\n return {\n type: ArgValueType.Literal,\n value: `${decl.getSourceFile().getFilePath()}:${expression.getText()}`,\n };\n }\n\n // その他の宣言タイプ(インポート宣言など)\n // ファイルパス + 変数名で識別する\n return {\n type: ArgValueType.Literal,\n value: `${decl.getSourceFile().getFilePath()}:${expression.getText()}`,\n };\n }\n }\n\n // リテラル型\n if (type.isStringLiteral() || type.isNumberLiteral()) {\n return {\n type: ArgValueType.Literal,\n value: JSON.stringify(type.getLiteralValue()),\n };\n }\n\n if (type.isBooleanLiteral()) {\n return { type: ArgValueType.Literal, value: type.getText() };\n }\n\n // CallExpression (例: this.method(), obj.method())\n // プロパティアクセスを伴うメソッド呼び出しは、実行時に異なる値を返す可能性があるため、\n // 使用箇所ごとにユニークな値として扱う\n // 例: declSourceFile.getFilePath() はループ内で異なる declSourceFile に対して呼ばれる可能性がある\n if (Node.isCallExpression(expression)) {\n const calleeExpr = expression.getExpression();\n if (Node.isPropertyAccessExpression(calleeExpr)) {\n const sourceFile = expression.getSourceFile();\n const line = expression.getStartLineNumber();\n return {\n type: ArgValueType.Literal,\n value: `[methodCall]${sourceFile.getFilePath()}:${line}:${expression.getText()}`,\n };\n }\n }\n\n return { type: ArgValueType.Literal, value: expression.getText() };\n}\n\n/**\n * JSX要素から呼び出し情報を抽出\n */\nfunction extractFromJsxElement(\n element: JsxOpeningElement | JsxSelfClosingElement,\n targetId: string,\n callSiteMap: CallSiteMap,\n): void {\n const sourceFile = element.getSourceFile();\n const filePath = sourceFile.getFilePath();\n\n let info = callSiteMap.get(targetId);\n if (!info) {\n info = new Map();\n callSiteMap.set(targetId, info);\n }\n\n for (const attr of element.getAttributes()) {\n if (!Node.isJsxAttribute(attr)) continue;\n\n const propName = attr.getNameNode().getText();\n const initializer = attr.getInitializer();\n\n let value: ArgValue;\n if (!initializer) {\n // boolean shorthand\n value = { type: ArgValueType.Literal, value: \"true\" };\n } else if (Node.isJsxExpression(initializer)) {\n const expr = initializer.getExpression();\n value = expr ? extractArgValue(expr) : { type: ArgValueType.Undefined };\n } else {\n value = { type: ArgValueType.Literal, value: initializer.getText() };\n }\n\n const args = info.get(propName) ?? [];\n args.push({\n name: propName,\n value,\n filePath,\n line: element.getStartLineNumber(),\n });\n info.set(propName, args);\n }\n}\n\n/**\n * 関数呼び出しから呼び出し情報を抽出\n */\nfunction extractFromCallExpression(\n callExpr: CallExpression,\n targetId: string,\n paramNames: string[],\n callSiteMap: CallSiteMap,\n): void {\n const sourceFile = callExpr.getSourceFile();\n const filePath = sourceFile.getFilePath();\n\n let info = callSiteMap.get(targetId);\n if (!info) {\n info = new Map();\n callSiteMap.set(targetId, info);\n }\n\n const args = callExpr.getArguments();\n for (let i = 0; i < paramNames.length; i++) {\n const paramName = paramNames[i];\n const arg = args[i];\n const value: ArgValue = arg\n ? extractArgValue(arg)\n : { type: ArgValueType.Undefined };\n\n const argList = info.get(paramName) ?? [];\n argList.push({\n name: paramName,\n value,\n filePath,\n line: callExpr.getStartLineNumber(),\n });\n info.set(paramName, argList);\n }\n}\n\n/**\n * ソースファイルからすべての呼び出し情報を収集する\n */\nexport function collectCallSites(\n sourceFiles: SourceFile[],\n shouldExcludeFile: FileFilter = isTestOrStorybookFile,\n): CallSiteMap {\n const callSiteMap: CallSiteMap = new Map();\n\n for (const sourceFile of sourceFiles) {\n if (shouldExcludeFile(sourceFile.getFilePath())) continue;\n\n // JSX要素を収集\n const jsxElements = [\n ...sourceFile.getDescendantsOfKind(SyntaxKind.JsxOpeningElement),\n ...sourceFile.getDescendantsOfKind(SyntaxKind.JsxSelfClosingElement),\n ];\n\n for (const element of jsxElements) {\n const tagName = element.getTagNameNode();\n if (!Node.isIdentifier(tagName)) continue;\n\n const symbol = tagName.getSymbol();\n // インポートを通じて実際の定義を解決する\n const resolvedSymbol = symbol?.getAliasedSymbol() ?? symbol;\n const decl = resolvedSymbol?.getDeclarations()[0];\n if (!decl) continue;\n\n const declSourceFile = decl.getSourceFile();\n const targetId = createTargetId(\n declSourceFile.getFilePath(),\n tagName.getText(),\n );\n\n extractFromJsxElement(element, targetId, callSiteMap);\n }\n\n // 関数呼び出しを収集\n const callExprs = sourceFile.getDescendantsOfKind(\n SyntaxKind.CallExpression,\n );\n\n for (const callExpr of callExprs) {\n const expr = callExpr.getExpression();\n if (!Node.isIdentifier(expr)) continue;\n\n const symbol = expr.getSymbol();\n const resolvedSymbol = symbol?.getAliasedSymbol() ?? symbol;\n const decl = resolvedSymbol?.getDeclarations()[0];\n if (!decl) continue;\n\n // 関数宣言またはアロー関数を含む変数宣言のみ\n // パラメータを取得\n let paramNames: string[] = [];\n\n if (Node.isFunctionDeclaration(decl)) {\n paramNames = decl.getParameters().map((p) => p.getName());\n } else if (Node.isVariableDeclaration(decl)) {\n const init = decl.getInitializer();\n if (init && Node.isArrowFunction(init)) {\n paramNames = init.getParameters().map((p) => p.getName());\n } else if (init && Node.isFunctionExpression(init)) {\n paramNames = init.getParameters().map((p) => p.getName());\n } else {\n continue;\n }\n } else {\n continue;\n }\n\n const declSourceFile = decl.getSourceFile();\n const targetId = createTargetId(\n declSourceFile.getFilePath(),\n expr.getText(),\n );\n\n extractFromCallExpression(callExpr, targetId, paramNames, callSiteMap);\n }\n }\n\n return callSiteMap;\n}\n","import type { Node } from \"ts-morph\";\nimport {\n type ArgValue,\n ArgValueType,\n type CallSiteMap,\n extractArgValue,\n} from \"./callSiteCollector\";\nimport { argValueToKey, resolveParameterValue } from \"./parameterUtils\";\n\n/**\n * 引数が渡されなかった場合を表す特別な値\n * 必須/任意を問わず、引数未指定の使用箇所を統一的に扱うために使用\n *\n * 注意: この値は文字列 \"[undefined]\" であり、JavaScriptの undefined とは異なる。\n * 解決関数が undefined を返す場合は「値を解決できなかった」ことを意味し、\n * この文字列を返す場合は「実際に undefined が渡された」ことを意味する。\n */\nexport const UNDEFINED_VALUE = \"[undefined]\";\n\n/**\n * 関数型の値を表すプレフィックス\n * コールバック関数など、関数が渡された場合は使用箇所ごとにユニークな値として扱う\n * これにより、同じコールバック関数を渡していても「定数」として検出されない\n */\nexport const FUNCTION_VALUE_PREFIX = \"[function]\";\n\n/**\n * resolveExpressionValueのコンテキスト\n */\nexport interface ResolveContext {\n /** 呼び出し情報(パラメータ参照の解決に使用) */\n callSiteMap: CallSiteMap;\n}\n\n/**\n * 式の実際の値を解決する\n *\n * 異なるファイルで同じenum値やリテラル値を使用している場合でも、\n * 同一の値として認識できるよう、値を正規化して文字列表現で返す。\n * 同名だが異なる定義(別ファイルの同名enum等)を区別するため、\n * 必要に応じてファイルパスを含めた識別子を返す。\n *\n * @param expression - 解決対象の式\n * @param context - 呼び出し情報などのコンテキスト\n */\nexport function resolveExpressionValue(\n expression: Node,\n context: ResolveContext,\n): string {\n const argValue = extractArgValue(expression);\n const sourceFile = expression.getSourceFile();\n const usageLocation = {\n filePath: sourceFile.getFilePath(),\n line: expression.getStartLineNumber(),\n };\n return argValueToString(argValue, context, usageLocation);\n}\n\n/**\n * 使用箇所の位置情報\n */\ninterface UsageLocation {\n filePath: string;\n line: number;\n}\n\n/**\n * ArgValue を文字列表現に変換する\n *\n * @param value - 変換対象の ArgValue\n * @param context - パラメータ参照解決用のコンテキスト\n * @param usageLocation - 使用箇所の位置情報(解決できないParamRefのユニーク化に使用)\n */\nfunction argValueToString(\n value: ArgValue,\n context: ResolveContext,\n usageLocation: UsageLocation,\n): string {\n switch (value.type) {\n case ArgValueType.Literal:\n return value.value;\n\n case ArgValueType.Function:\n // 関数型は使用箇所ごとにユニークな値として扱う\n return `${FUNCTION_VALUE_PREFIX}${value.filePath}:${value.line}`;\n\n case ArgValueType.ParamRef: {\n // パラメータ参照は callSiteMap を使って解決を試みる\n const resolved = resolveParameterValue(value, context.callSiteMap);\n if (resolved !== undefined) {\n return argValueToKey(resolved);\n }\n // 解決できない場合は使用箇所ごとにユニークな値として扱う\n // 使用箇所の位置情報を含めてユニークにする\n return `[paramRef]${usageLocation.filePath}:${usageLocation.line}:${argValueToKey(value)}`;\n }\n\n case ArgValueType.Undefined:\n return UNDEFINED_VALUE;\n }\n}\n","import { Node, type ObjectLiteralExpression, type Type } from \"ts-morph\";\nimport {\n type ResolveContext,\n resolveExpressionValue,\n UNDEFINED_VALUE,\n} from \"@/extraction/resolveExpressionValue\";\n\nexport type FlattenedValue = { key: string; value: string };\n\n/**\n * オブジェクトリテラルを再帰的に解析し、フラットなkey-valueペアを返す\n *\n * 期待される型(コンテキスト型)から省略されたプロパティも検出し、\n * [undefined] として記録する。\n *\n * @param expression - 解析対象の式ノード\n * @param prefix - キー名のプレフィックス(ネストしたプロパティの親パスを表す)\n * @param context - 呼び出し情報などのコンテキスト\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 context: ResolveContext,\n): FlattenedValue[] {\n if (!Node.isObjectLiteralExpression(expression)) {\n // オブジェクトリテラル以外の場合は単一の値として返す\n return [\n { key: prefix, value: resolveExpressionValue(expression, context) },\n ];\n }\n\n // 渡されたプロパティを収集\n const existingValues = flattenExistingProperties(expression, prefix, context);\n\n // 期待される型から省略されたプロパティを検出\n // ユニオン型(例: `{ a: number } | undefined`)からオブジェクト型を抽出\n const contextualType = expression.getContextualType();\n const objectType = contextualType ? extractObjectType(contextualType) : null;\n const missingValues = objectType\n ? getMissingProperties(expression, objectType, prefix)\n : [];\n\n return [...existingValues, ...missingValues];\n}\n\n/**\n * オブジェクトリテラル内の既存プロパティをフラット化する\n */\nfunction flattenExistingProperties(\n expression: ObjectLiteralExpression,\n prefix: string,\n context: ResolveContext,\n): FlattenedValue[] {\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, context)\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(), context),\n },\n ];\n }\n\n return [];\n });\n}\n\n/**\n * 期待される型と比較して、省略されたプロパティを検出する\n *\n * 省略されたプロパティがオブジェクト型の場合、そのネストプロパティも\n * 再帰的に [undefined] として出力する。これにより、親プロパティが\n * 省略された場合でも、ネストプロパティが他の呼び出しと比較可能になる。\n *\n * @param objectExpression - オブジェクトリテラル\n * @param expectedType - 期待される型(コンテキスト型)\n * @param prefix - キー名のプレフィックス\n * @returns 省略されたプロパティの配列(値は [undefined])\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 // 省略されたプロパティを [undefined] として記録\n missingValues.push({\n key: nestedPrefix,\n value: UNDEFINED_VALUE,\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 * 省略された親プロパティのネストプロパティを再帰的に [undefined] として出力\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: UNDEFINED_VALUE,\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\n/**\n * 型からオブジェクト部分を抽出する\n * ユニオン型(例: `{ a: number } | undefined`)から `undefined` を除外して\n * オブジェクト型部分を返す。オブジェクト型でない場合は null を返す。\n */\nfunction 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 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 { type ResolveContext, UNDEFINED_VALUE } from \"./resolveExpressionValue\";\n\n/**\n * 使用状況を抽出するユーティリティクラス\n */\nexport class ExtractUsages {\n /**\n * 関数呼び出しから引数の使用状況を抽出する\n *\n * オブジェクトリテラルの場合は再帰的にフラット化し、\n * 各プロパティを「引数名.プロパティ名」形式で記録する。\n *\n * @param callExpression - 関数呼び出しノード\n * @param callable - 対象の関数情報\n * @param context - 呼び出し情報などのコンテキスト\n * @returns 引数使用状況の配列\n */\n static fromCall(\n callExpression: CallExpression,\n callable: Exported,\n context: ResolveContext,\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 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(\n arg,\n param.name,\n context,\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 context - 呼び出し情報などのコンテキスト\n * @returns props使用状況の配列\n */\n static fromJsxElement(\n element: JsxOpeningElement | JsxSelfClosingElement,\n definitions: Definition[],\n context: ResolveContext,\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 context,\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 * 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 { UNDEFINED_VALUE } from \"@/extraction/resolveExpressionValue\";\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 * enum値の文字列表現パターン\n * 形式: \"filePath:EnumName.MemberName=value\"\n */\nconst ENUM_PATTERN = /^.+:\\w+\\.\\w+=.+$/;\n\n/**\n * 値の文字列表現から種別を判定する\n *\n * @param value - resolveExpressionValue で得られた値の文字列表現\n * @returns 検出された種別、または判定不能な場合は null\n */\nexport function detectValueType(value: string): ValueType | null {\n // boolean: \"true\" または \"false\"\n if (value === \"true\" || value === \"false\") {\n return \"boolean\";\n }\n\n // undefined: \"[undefined]\"\n if (value === UNDEFINED_VALUE) {\n return \"undefined\";\n }\n\n // enum: \"filePath:EnumName.MemberName=value\" 形式\n if (ENUM_PATTERN.test(value)) {\n return \"enum\";\n }\n\n // string: ダブルクォート囲み(JSON.stringify形式)\n if (value.startsWith('\"') && value.endsWith('\"')) {\n return \"string\";\n }\n\n // number: 数値として解釈可能\n // 空文字列は Number(\"\") === 0 となるため除外\n if (value !== \"\" && !Number.isNaN(Number(value))) {\n return \"number\";\n }\n\n // 判定不能(function, paramRef, this, 変数参照など)\n return null;\n}\n\n/**\n * 値が指定された種別に含まれるか判定する\n *\n * @param value - resolveExpressionValue で得られた値の文字列表現\n * @param allowedTypes - 許可する種別の配列、または \"all\"\n * @returns 指定種別に含まれる場合は true\n */\nexport function matchesValueTypes(\n value: string,\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","import {\n type Identifier,\n Node,\n type ParameterDeclaration,\n type ReferencedSymbol,\n} from \"ts-morph\";\nimport type { CallSiteMap } from \"@/extraction/callSiteCollector\";\nimport {\n FUNCTION_VALUE_PREFIX,\n type ResolveContext,\n} from \"@/extraction/resolveExpressionValue\";\nimport { isTestOrStorybookFile } from \"@/source/fileFilters\";\nimport type {\n AnalysisResult,\n AnalyzerOptions,\n ClassifiedDeclaration,\n Constant,\n Definition,\n Exported,\n FileFilter,\n Usage,\n ValueType,\n} from \"@/types\";\nimport { getSingleValueFromSet } from \"@/utils/getSingleValueFromSet\";\nimport { matchesValueTypes } from \"@/utils/valueTypeDetector\";\n\n/**\n * ts-morph の参照情報を表す型\n */\ntype ReferenceEntry = ReturnType<ReferencedSymbol[\"getReferences\"]>[number];\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 totalCallCount: number;\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 protected valueTypes: 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.valueTypes = options.valueTypes ?? \"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 getResolveContext(): ResolveContext {\n return {\n callSiteMap: this.callSiteMap,\n };\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 * @param groupedUsages - 使用状況のグループ(パラメータ名 → 使用状況配列)\n * @param usages - 追加する使用状況の配列\n */\n protected addUsagesToGroup(\n groupedUsages: Record<string, Usage[]>,\n usages: Usage[],\n ): void {\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 /**\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 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\n // 総呼び出し回数を計算(最大のUsage配列の長さを使用)\n // すべての呼び出しで存在するパラメータのUsage数が基準となる\n const totalCallCount = Math.max(\n ...Object.values(item.usages).map((usages) => usages.length),\n 0,\n );\n\n fileMap.set(item.name, {\n line: item.sourceLine,\n params: paramMap,\n totalCallCount,\n });\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 // 定数として認識する条件:\n // 1. 使用回数が最小使用回数以上\n // 2. すべての使用箇所で同じ値\n // 3. Usage数が総呼び出し回数と一致(すべての呼び出しで値が存在)\n // これにより、オプショナルなプロパティが一部の呼び出しでのみ\n // 指定されている場合を定数として誤検出しない\n const isConstant =\n usageData.usages.length >= this.minUsages &&\n usageData.values.size === 1 &&\n usageData.usages.length === targetInfo.totalCallCount;\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 // 値種別によるフィルタリング\n if (!matchesValueTypes(value, this.valueTypes)) {\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 } from \"ts-morph\";\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 * クラスメソッドの引数分析を行う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 declarations - 事前分類済みの宣言配列(type: \"class\")\n * @returns クラスメソッドとその使用状況の配列(名前は「ClassName.methodName」形式)\n */\n protected collect(declarations: ClassifiedDeclaration[]): Exported[] {\n const results: Exported[] = [];\n\n for (const classified of declarations) {\n const { exportName, sourceFile, declaration } = classified;\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 callable: Exported = {\n name: `${exportName}.${methodName}`,\n sourceFilePath: sourceFile.getFilePath(),\n sourceLine: method.getStartLineNumber(),\n definitions: parameters,\n declaration: method,\n usages: {},\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 const groupedUsages: Record<string, Usage[]> = {};\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 callable,\n this.getResolveContext(),\n );\n this.addUsagesToGroup(groupedUsages, usages);\n }\n\n callable.usages = groupedUsages;\n results.push(callable);\n }\n }\n\n return results;\n }\n}\n","import { Node, SyntaxKind } from \"ts-morph\";\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 * 関数の引数分析を行う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 declarations - 事前分類済みの宣言配列(type: \"function\")\n * @returns exportされた関数とその使用状況の配列\n */\n protected collect(declarations: ClassifiedDeclaration[]): Exported[] {\n const results: 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 = this.findFilteredReferences(nameNode);\n\n // 関数の宣言からパラメータ定義を取得\n const parameters = this.getParameterDefinitions(declaration);\n\n const callable: Exported = {\n name: exportName,\n sourceFilePath: sourceFile.getFilePath(),\n sourceLine: declaration.getStartLineNumber(),\n definitions: parameters,\n declaration,\n usages: {},\n };\n\n // 参照から関数呼び出しを抽出し、usagesをパラメータ名ごとにグループ化\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 // 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 callable,\n this.getResolveContext(),\n );\n this.addUsagesToGroup(groupedUsages, usages);\n }\n\n callable.usages = groupedUsages;\n results.push(callable);\n }\n\n return results;\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 { ClassMethodAnalyzer } from \"@/analyzer/classMethodAnalyzer\";\nimport { FunctionAnalyzer } from \"@/analyzer/functionAnalyzer\";\nimport type { CallSiteMap } from \"@/extraction/callSiteCollector\";\nimport { classifyDeclarations } from \"@/source/classifyDeclarations\";\nimport { isTestOrStorybookFile } from \"@/source/fileFilters\";\nimport type { AnalysisResult, FileFilter, ValueType } from \"@/types\";\n\ninterface AnalyzeFunctionsOptions {\n shouldExcludeFile?: FileFilter;\n minUsages?: number;\n /** 検出対象の値種別。デフォルト: \"all\" */\n valueTypes?: 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 valueTypes = \"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, valueTypes };\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 constants: [...functionResult.constants, ...classMethodResult.constants],\n exported: [...functionResult.exported, ...classMethodResult.exported],\n };\n}\n","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, 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 = this.findFilteredReferences(nameNode);\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 this.getResolveContext(),\n );\n this.addUsagesToGroup(groupedUsages, usages);\n }\n\n component.usages = groupedUsages;\n exportedComponents.push(component);\n }\n\n return exportedComponents;\n }\n}\n","import type { SourceFile } from \"ts-morph\";\nimport { ComponentAnalyzer } from \"@/analyzer/componentAnalyzer\";\nimport type { CallSiteMap } from \"@/extraction/callSiteCollector\";\nimport { classifyDeclarations } from \"@/source/classifyDeclarations\";\nimport { isTestOrStorybookFile } from \"@/source/fileFilters\";\nimport type { AnalysisResult, FileFilter, ValueType } from \"@/types\";\n\ninterface AnalyzePropsOptions {\n shouldExcludeFile?: FileFilter;\n minUsages?: number;\n /** 検出対象の値種別。デフォルト: \"all\" */\n valueTypes?: 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 valueTypes = \"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 valueTypes,\n });\n analyzer.setCallSiteMap(callSiteMap);\n\n return analyzer.analyze(components);\n}\n"],"mappings":"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"}
|
package/dist/cli.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
2
|
+
import { a as isTestOrStorybookFile, i as collectCallSites, n as analyzeFunctionsCore, r as VALID_VALUE_TYPES, t as analyzePropsCore } from "./analyzeProps-6k4QTasK.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.valueTypes = "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.valueTypes = 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
|
+
valueTypes: "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.valueTypes = 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:
|
|
@@ -341,12 +360,13 @@ async function main() {
|
|
|
341
360
|
if (error instanceof Error) exitWithError(error.message);
|
|
342
361
|
throw error;
|
|
343
362
|
}
|
|
344
|
-
const { targetDir, minUsages, target, output, tsconfig } = {
|
|
363
|
+
const { targetDir, minUsages, target, output, tsconfig, valueTypes } = {
|
|
345
364
|
targetDir: path.resolve(cliOptions.targetDir ?? fileConfig.targetDir ?? DEFAULT_OPTIONS.targetDir),
|
|
346
365
|
minUsages: cliOptions.minUsages ?? fileConfig.minUsages ?? DEFAULT_OPTIONS.minUsages,
|
|
347
366
|
target: cliOptions.target ?? fileConfig.target ?? DEFAULT_OPTIONS.target,
|
|
348
367
|
output: cliOptions.output ?? fileConfig.output ?? DEFAULT_OPTIONS.output,
|
|
349
|
-
tsconfig: cliOptions.tsconfig ?? fileConfig.tsconfig ?? DEFAULT_OPTIONS.tsconfig
|
|
368
|
+
tsconfig: cliOptions.tsconfig ?? fileConfig.tsconfig ?? DEFAULT_OPTIONS.tsconfig,
|
|
369
|
+
valueTypes: cliOptions.valueTypes ?? fileConfig.valueTypes ?? DEFAULT_OPTIONS.valueTypes
|
|
350
370
|
};
|
|
351
371
|
try {
|
|
352
372
|
validateTargetDir(targetDir);
|
|
@@ -372,6 +392,7 @@ async function main() {
|
|
|
372
392
|
if (target === "all" || target === "components") {
|
|
373
393
|
const propsResult = analyzePropsCore(sourceFilesToAnalyze, {
|
|
374
394
|
minUsages,
|
|
395
|
+
valueTypes,
|
|
375
396
|
callSiteMap
|
|
376
397
|
});
|
|
377
398
|
allExported.push(...propsResult.exported);
|
|
@@ -380,6 +401,7 @@ async function main() {
|
|
|
380
401
|
if (target === "all" || target === "functions") {
|
|
381
402
|
const functionsResult = analyzeFunctionsCore(sourceFilesToAnalyze, {
|
|
382
403
|
minUsages,
|
|
404
|
+
valueTypes,
|
|
383
405
|
callSiteMap
|
|
384
406
|
});
|
|
385
407
|
allExported.push(...functionsResult.exported);
|
package/dist/cli.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cli.mjs","names":["config: unknown","VALID_TARGETS: readonly AnalyzeMode[]","VALID_OUTPUTS: readonly OutputMode[]","result: DittoryConfig","VALID_TARGETS","VALID_OUTPUTS","DEFAULT_OPTIONS: ResolvedOptions","VALID_TARGETS: readonly AnalyzeMode[]","VALID_OUTPUTS: readonly OutputMode[]","VALID_OPTIONS: readonly string[]","result: RawCliOptions","cliOptions: ReturnType<typeof parseCliOptions>","fileConfig: Awaited<ReturnType<typeof loadConfig>>","allExported: AnalysisResult[\"exported\"]","allConstants: AnalysisResult[\"constants\"]"],"sources":["../src/cli/loadConfig.ts","../src/cli/parseCliOptions.ts","../src/output/printAnalysisResult.ts","../src/source/createFilteredSourceFiles.ts","../src/cli.ts"],"sourcesContent":["import fs from \"node:fs\";\nimport path from \"node:path\";\nimport { pathToFileURL } from \"node:url\";\nimport type { AnalyzeMode, OutputMode } from \"./parseCliOptions\";\n\n/** コンフィグファイルの検索順序 */\nconst CONFIG_FILE_NAMES = [\n \"dittory.config.js\",\n \"dittory.config.mjs\",\n \"dittory.config.json\",\n] as const;\n\n/**\n * コンフィグファイルの設定項目\n */\nexport interface DittoryConfig {\n minUsages?: number;\n target?: AnalyzeMode;\n output?: OutputMode;\n tsconfig?: string;\n targetDir?: string;\n}\n\n/**\n * コンフィグファイルを読み込む\n *\n * 現在の作業ディレクトリから以下の順序でコンフィグファイルを探す:\n * 1. dittory.config.js\n * 2. dittory.config.mjs\n * 3. dittory.config.json\n *\n * ファイルが存在しない場合は空のオブジェクトを返す。\n *\n * @returns コンフィグオブジェクト\n * @throws {Error} コンフィグファイルの読み込みに失敗した場合\n */\nexport async function loadConfig(): Promise<DittoryConfig> {\n const cwd = process.cwd();\n\n for (const fileName of CONFIG_FILE_NAMES) {\n const configPath = path.join(cwd, fileName);\n\n if (!fs.existsSync(configPath)) {\n continue;\n }\n\n if (fileName.endsWith(\".json\")) {\n return loadJsonConfig(configPath);\n }\n return loadJsConfig(configPath);\n }\n\n return {};\n}\n\n/**\n * JSON コンフィグを読み込む\n */\nfunction loadJsonConfig(configPath: string): DittoryConfig {\n const content = fs.readFileSync(configPath, \"utf-8\");\n\n try {\n const config: unknown = JSON.parse(content);\n\n if (typeof config !== \"object\" || config === null) {\n throw new Error(`Invalid config: expected object, got ${typeof config}`);\n }\n\n return validateConfig(config as Record<string, unknown>);\n } catch (error) {\n if (error instanceof SyntaxError) {\n throw new Error(\n `Failed to parse ${path.basename(configPath)}: ${error.message}`,\n );\n }\n throw error;\n }\n}\n\n/**\n * JS コンフィグを読み込む\n */\nasync function loadJsConfig(configPath: string): Promise<DittoryConfig> {\n try {\n // Windows 対応のため file:// URL に変換\n const fileUrl = pathToFileURL(configPath).href;\n const module = (await import(fileUrl)) as { default?: unknown };\n const config = module.default;\n\n if (typeof config !== \"object\" || config === null) {\n throw new Error(`Invalid config: expected object, got ${typeof config}`);\n }\n\n return validateConfig(config as Record<string, unknown>);\n } catch (error) {\n if (error instanceof Error) {\n throw new Error(\n `Failed to load ${path.basename(configPath)}: ${error.message}`,\n );\n }\n throw error;\n }\n}\n\nconst VALID_TARGETS: readonly AnalyzeMode[] = [\n \"all\",\n \"components\",\n \"functions\",\n];\nconst VALID_OUTPUTS: readonly OutputMode[] = [\"simple\", \"verbose\"];\n\n/**\n * コンフィグの値を検証する\n */\nfunction validateConfig(config: Record<string, unknown>): DittoryConfig {\n const result: DittoryConfig = {};\n\n if (\"minUsages\" in config) {\n if (typeof config.minUsages !== \"number\" || config.minUsages < 1) {\n throw new Error(\n `Invalid config: minUsages must be a number >= 1, got ${config.minUsages}`,\n );\n }\n result.minUsages = config.minUsages;\n }\n\n if (\"target\" in config) {\n if (!VALID_TARGETS.includes(config.target as AnalyzeMode)) {\n throw new Error(\n `Invalid config: target must be one of ${VALID_TARGETS.join(\", \")}, got ${config.target}`,\n );\n }\n result.target = config.target as AnalyzeMode;\n }\n\n if (\"output\" in config) {\n if (!VALID_OUTPUTS.includes(config.output as OutputMode)) {\n throw new Error(\n `Invalid config: output must be one of ${VALID_OUTPUTS.join(\", \")}, got ${config.output}`,\n );\n }\n result.output = config.output as OutputMode;\n }\n\n if (\"tsconfig\" in config) {\n if (typeof config.tsconfig !== \"string\" || config.tsconfig === \"\") {\n throw new Error(\n `Invalid config: tsconfig must be a non-empty string, got ${config.tsconfig}`,\n );\n }\n result.tsconfig = config.tsconfig;\n }\n\n if (\"targetDir\" in config) {\n if (typeof config.targetDir !== \"string\" || config.targetDir === \"\") {\n throw new Error(\n `Invalid config: targetDir must be a non-empty string, got ${config.targetDir}`,\n );\n }\n result.targetDir = config.targetDir;\n }\n\n return result;\n}\n","import fs from \"node:fs\";\n\nexport type AnalyzeMode = \"all\" | \"components\" | \"functions\";\nexport type OutputMode = \"simple\" | \"verbose\";\n\n/**\n * CLI で明示的に指定されたオプション(デフォルト値なし)\n */\nexport interface RawCliOptions {\n targetDir?: string;\n minUsages?: number;\n target?: AnalyzeMode;\n output?: OutputMode;\n tsconfig?: string;\n showHelp: boolean;\n}\n\n/**\n * 解決済みのオプション(デフォルト値適用後)\n */\nexport interface ResolvedOptions {\n targetDir: string;\n minUsages: number;\n target: AnalyzeMode;\n output: OutputMode;\n tsconfig: string;\n}\n\n/** デフォルトのオプション値 */\nexport const DEFAULT_OPTIONS: ResolvedOptions = {\n targetDir: \"./src\",\n minUsages: 2,\n target: \"all\",\n output: \"simple\",\n tsconfig: \"./tsconfig.json\",\n};\n\nexport class CliValidationError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"CliValidationError\";\n }\n}\n\nconst VALID_TARGETS: readonly AnalyzeMode[] = [\n \"all\",\n \"components\",\n \"functions\",\n];\n\nconst VALID_OUTPUTS: readonly OutputMode[] = [\"simple\", \"verbose\"];\n\n/** 不明なオプションの検出に使用 */\nconst VALID_OPTIONS: readonly string[] = [\n \"--min\",\n \"--target\",\n \"--output\",\n \"--tsconfig\",\n \"--help\",\n];\n\n/**\n * CLIオプションをパースする\n *\n * 明示的に指定されたオプションのみを返す(デフォルト値は含まない)\n *\n * @throws {CliValidationError} オプションが無効な場合\n */\nexport function parseCliOptions(args: string[]): RawCliOptions {\n const result: RawCliOptions = { showHelp: false };\n\n for (const arg of args) {\n if (arg === \"--help\") {\n result.showHelp = true;\n continue;\n }\n\n if (arg.startsWith(\"--min=\")) {\n const valueStr = arg.slice(6);\n const value = Number.parseInt(valueStr, 10);\n\n if (valueStr === \"\" || Number.isNaN(value)) {\n throw new CliValidationError(\n `Invalid value for --min: \"${valueStr}\" (must be a number)`,\n );\n }\n if (value < 1) {\n throw new CliValidationError(`--min must be at least 1: ${value}`);\n }\n\n result.minUsages = value;\n } else if (arg.startsWith(\"--target=\")) {\n const value = arg.slice(9);\n\n if (!VALID_TARGETS.includes(value as AnalyzeMode)) {\n throw new CliValidationError(\n `Invalid value for --target: \"${value}\" (valid values: ${VALID_TARGETS.join(\n \", \",\n )})`,\n );\n }\n\n result.target = value as AnalyzeMode;\n } else if (arg.startsWith(\"--output=\")) {\n const value = arg.slice(9);\n\n if (!VALID_OUTPUTS.includes(value as OutputMode)) {\n throw new CliValidationError(\n `Invalid value for --output: \"${value}\" (valid values: ${VALID_OUTPUTS.join(\n \", \",\n )})`,\n );\n }\n\n result.output = value as OutputMode;\n } else if (arg.startsWith(\"--tsconfig=\")) {\n const value = arg.slice(11);\n\n if (value === \"\") {\n throw new CliValidationError(\n \"Invalid value for --tsconfig: path cannot be empty\",\n );\n }\n\n result.tsconfig = value;\n } else if (arg.startsWith(\"--\")) {\n const optionName = arg.split(\"=\")[0];\n\n if (!VALID_OPTIONS.includes(optionName)) {\n throw new CliValidationError(`Unknown option: ${optionName}`);\n }\n } else {\n result.targetDir = arg;\n }\n }\n\n return result;\n}\n\n/**\n * 対象ディレクトリの存在を検証する\n *\n * @throws {CliValidationError} ディレクトリが存在しない、またはディレクトリでない場合\n */\nexport function validateTargetDir(targetDir: string): void {\n if (!fs.existsSync(targetDir)) {\n throw new CliValidationError(`Directory does not exist: ${targetDir}`);\n }\n\n const stat = fs.statSync(targetDir);\n if (!stat.isDirectory()) {\n throw new CliValidationError(`Path is not a directory: ${targetDir}`);\n }\n}\n\n/**\n * tsconfig.json の存在を検証する\n *\n * @throws {CliValidationError} ファイルが存在しない場合\n */\nexport function validateTsConfig(tsConfigPath: string): void {\n if (!fs.existsSync(tsConfigPath)) {\n throw new CliValidationError(`tsconfig not found: ${tsConfigPath}`);\n }\n}\n\n/**\n * ヘルプメッセージを取得する\n */\nexport function getHelpMessage(): string {\n return `\nUsage: dittory [options] [directory]\n\nOptions:\n --min=<number> Minimum usage count (default: 2)\n --target=<mode> Analysis target: all, components, functions (default: all)\n --output=<mode> Output mode: simple, verbose (default: simple)\n --tsconfig=<path> Path to tsconfig.json (default: ./tsconfig.json)\n --help Show this help message\n\nArguments:\n directory Target directory to analyze (default: ./src)\n`;\n}\n","import path from \"node:path\";\nimport type { OutputMode } from \"@/cli/parseCliOptions\";\nimport type { AnalysisResult, Constant, Exported } from \"@/types\";\n\nfunction bold(text: string): string {\n return `\\x1b[1m${text}\\x1b[0m`;\n}\n\nfunction green(text: string): string {\n return `\\x1b[32m${text}\\x1b[0m`;\n}\n\n/**\n * 値を表示用にフォーマットする\n *\n * 内部的にはenum区別のためにファイルパスを含むが、表示時は不要なので除去する\n * 例: \"/path/to/file.ts:ButtonVariant.Primary=\\\"primary\\\"\" → \"ButtonVariant.Primary\"\n */\nfunction formatValueForDisplay(value: string): string {\n // enum形式: \"ファイルパス:EnumName.MemberName=値\" のパターンをチェック\n const enumMatch = value.match(/^.+:(\\w+\\.\\w+)=.+$/);\n if (enumMatch) {\n return enumMatch[1];\n }\n return value;\n}\n\n/**\n * グループ化された定数情報\n */\ninterface GroupedConstant {\n targetName: string;\n targetSourceFile: string;\n targetLine: number;\n params: Array<{\n paramName: string;\n value: string;\n usageCount: number;\n usages: Constant[\"usages\"];\n }>;\n}\n\n/**\n * Constant[]を関数/コンポーネント単位でグループ化する\n */\nfunction groupConstantsByTarget(constants: Constant[]): GroupedConstant[] {\n const groupMap = new Map<string, GroupedConstant>();\n\n for (const constant of constants) {\n const key = `${constant.targetSourceFile}:${constant.targetName}`;\n\n let group = groupMap.get(key);\n if (!group) {\n group = {\n targetName: constant.targetName,\n targetSourceFile: constant.targetSourceFile,\n targetLine: constant.targetLine,\n params: [],\n };\n groupMap.set(key, group);\n }\n\n group.params.push({\n paramName: constant.paramName,\n value: constant.value,\n usageCount: constant.usages.length,\n usages: constant.usages,\n });\n }\n\n return Array.from(groupMap.values());\n}\n\n/**\n * exportされた関数の一覧を出力\n */\nfunction printExportedFunctions(exported: Exported[]): void {\n const lines = [\n \"Collecting exported functions...\",\n ` → Found ${exported.length} function(s)`,\n ...exported.map(\n (fn) =>\n ` - ${bold(green(fn.name))} (${path.relative(\n process.cwd(),\n fn.sourceFilePath,\n )})`,\n ),\n \"\",\n ];\n console.log(lines.join(\"\\n\"));\n}\n\n/**\n * 常に同じ値が渡されている引数を出力\n */\nfunction printConstantArguments(constants: Constant[]): void {\n if (constants.length === 0) {\n return;\n }\n\n const grouped = groupConstantsByTarget(constants);\n\n for (const group of grouped) {\n const relativePath = path.relative(process.cwd(), group.targetSourceFile);\n const usageCount = group.params[0]?.usageCount ?? 0;\n // 使用箇所は全パラメータで同じなので、最初のパラメータから取得\n const usages = group.params[0]?.usages ?? [];\n\n console.log(\n `${bold(green(group.targetName))} ${relativePath}:${group.targetLine}`,\n );\n console.log(\"Constant Arguments:\");\n\n for (const param of group.params) {\n console.log(\n ` - ${param.paramName} = ${formatValueForDisplay(param.value)}`,\n );\n }\n\n console.log(`Usages (${usageCount}):`);\n for (const usage of usages) {\n const usagePath = path.relative(process.cwd(), usage.usageFilePath);\n console.log(` - ${usagePath}:${usage.usageLine}`);\n }\n\n console.log(\"\\n\");\n }\n}\n\n/**\n * 統計情報を出力\n */\nfunction printStatistics(result: AnalysisResult): void {\n const totalFunctions = result.exported.length;\n const functionsWithConstants = groupConstantsByTarget(\n result.constants,\n ).length;\n\n console.log(\"---\");\n console.log(\n `Found ${functionsWithConstants} function(s) with constant arguments out of ${totalFunctions} function(s).`,\n );\n}\n\n/**\n * 解析結果を出力\n */\nexport function printAnalysisResult(\n result: AnalysisResult,\n mode: OutputMode,\n): void {\n if (mode === \"verbose\") {\n printExportedFunctions(result.exported);\n }\n\n if (result.constants.length === 0) {\n console.log(\"No arguments with constant values were found.\");\n } else {\n printConstantArguments(result.constants);\n }\n\n printStatistics(result);\n}\n","import path from \"node:path\";\nimport { Project, type SourceFile } from \"ts-morph\";\nimport { isTestOrStorybookFile } from \"@/source/fileFilters\";\nimport type { FileFilter } from \"@/types\";\n\nexport interface CreateFilteredSourceFilesOptions {\n shouldExcludeFile?: FileFilter;\n tsConfigFilePath?: string;\n}\n\n/**\n * プロジェクトを初期化し、フィルタリングされたソースファイルを取得する\n */\nexport function createFilteredSourceFiles(\n targetDir: string,\n options: CreateFilteredSourceFilesOptions = {},\n): SourceFile[] {\n const {\n shouldExcludeFile = isTestOrStorybookFile,\n tsConfigFilePath = path.join(process.cwd(), \"tsconfig.json\"),\n } = options;\n\n // プロジェクトを初期化\n const project = new Project({\n tsConfigFilePath,\n skipAddingFilesFromTsConfig: true,\n });\n\n // 対象ディレクトリのファイルを追加\n project.addSourceFilesAtPaths(`${targetDir}/**/*.{ts,tsx,js,jsx}`);\n\n // ファイルをフィルタリング\n const allSourceFiles = project.getSourceFiles();\n const sourceFilesToAnalyze = allSourceFiles.filter(\n (sourceFile) => !shouldExcludeFile(sourceFile.getFilePath()),\n );\n\n return sourceFilesToAnalyze;\n}\n","#!/usr/bin/env node\nimport path from \"node:path\";\nimport { analyzeFunctionsCore } from \"@/analyzeFunctions\";\nimport { analyzePropsCore } from \"@/analyzeProps\";\nimport { loadConfig } from \"@/cli/loadConfig\";\nimport {\n CliValidationError,\n DEFAULT_OPTIONS,\n getHelpMessage,\n parseCliOptions,\n type ResolvedOptions,\n validateTargetDir,\n validateTsConfig,\n} from \"@/cli/parseCliOptions\";\nimport { collectCallSites } from \"@/extraction/callSiteCollector\";\nimport { printAnalysisResult } from \"@/output/printAnalysisResult\";\nimport { createFilteredSourceFiles } from \"@/source/createFilteredSourceFiles\";\nimport type { AnalysisResult } from \"@/types\";\n\n/**\n * エラーメッセージを表示してプロセスを終了する\n */\nfunction exitWithError(message: string): never {\n console.error(`Error: ${message}`);\n process.exit(1);\n}\n\nasync function main(): Promise<void> {\n // CLI オプションをパース\n let cliOptions: ReturnType<typeof parseCliOptions>;\n try {\n cliOptions = parseCliOptions(process.argv.slice(2));\n } catch (error) {\n if (error instanceof CliValidationError) {\n exitWithError(error.message);\n }\n throw error;\n }\n\n if (cliOptions.showHelp) {\n console.log(getHelpMessage());\n process.exit(0);\n }\n\n // コンフィグファイルを読み込む\n let fileConfig: Awaited<ReturnType<typeof loadConfig>>;\n try {\n fileConfig = await loadConfig();\n } catch (error) {\n if (error instanceof Error) {\n exitWithError(error.message);\n }\n throw error;\n }\n\n // オプションをマージ: CLI > コンフィグ > デフォルト\n const options: ResolvedOptions = {\n targetDir: path.resolve(\n cliOptions.targetDir ?? fileConfig.targetDir ?? DEFAULT_OPTIONS.targetDir,\n ),\n minUsages:\n cliOptions.minUsages ?? fileConfig.minUsages ?? DEFAULT_OPTIONS.minUsages,\n target: cliOptions.target ?? fileConfig.target ?? DEFAULT_OPTIONS.target,\n output: cliOptions.output ?? fileConfig.output ?? DEFAULT_OPTIONS.output,\n tsconfig:\n cliOptions.tsconfig ?? fileConfig.tsconfig ?? DEFAULT_OPTIONS.tsconfig,\n };\n\n const { targetDir, minUsages, target, output, tsconfig } = options;\n\n // 対象ディレクトリの存在を検証\n try {\n validateTargetDir(targetDir);\n } catch (error) {\n if (error instanceof CliValidationError) {\n exitWithError(error.message);\n }\n throw error;\n }\n\n // tsconfig.json の存在を検証\n try {\n validateTsConfig(tsconfig);\n } catch (error) {\n if (error instanceof CliValidationError) {\n exitWithError(error.message);\n }\n throw error;\n }\n\n if (output === \"verbose\") {\n console.log(`Target directory: ${targetDir}`);\n console.log(`Minimum usage count: ${minUsages}`);\n console.log(`Analysis target: ${target}\\n`);\n }\n\n const sourceFilesToAnalyze = createFilteredSourceFiles(targetDir, {\n tsConfigFilePath: tsconfig,\n });\n\n // 呼び出し情報を事前収集(パラメータ経由で渡された値を解決するために使用)\n const callSiteMap = collectCallSites(sourceFilesToAnalyze);\n\n // 各解析結果を収集\n const allExported: AnalysisResult[\"exported\"] = [];\n const allConstants: AnalysisResult[\"constants\"] = [];\n\n if (target === \"all\" || target === \"components\") {\n const propsResult = analyzePropsCore(sourceFilesToAnalyze, {\n minUsages,\n callSiteMap,\n });\n allExported.push(...propsResult.exported);\n allConstants.push(...propsResult.constants);\n }\n\n if (target === \"all\" || target === \"functions\") {\n const functionsResult = analyzeFunctionsCore(sourceFilesToAnalyze, {\n minUsages,\n callSiteMap,\n });\n allExported.push(...functionsResult.exported);\n allConstants.push(...functionsResult.constants);\n }\n\n const result: AnalysisResult = {\n exported: allExported,\n constants: allConstants,\n };\n\n printAnalysisResult(result, output);\n}\n\nmain().catch((error) => {\n console.error(error);\n process.exit(1);\n});\n"],"mappings":";;;;;;;;;AAMA,MAAM,oBAAoB;CACxB;CACA;CACA;CACD;;;;;;;;;;;;;;AA0BD,eAAsB,aAAqC;CACzD,MAAM,MAAM,QAAQ,KAAK;AAEzB,MAAK,MAAM,YAAY,mBAAmB;EACxC,MAAM,aAAa,KAAK,KAAK,KAAK,SAAS;AAE3C,MAAI,CAAC,GAAG,WAAW,WAAW,CAC5B;AAGF,MAAI,SAAS,SAAS,QAAQ,CAC5B,QAAO,eAAe,WAAW;AAEnC,SAAO,aAAa,WAAW;;AAGjC,QAAO,EAAE;;;;;AAMX,SAAS,eAAe,YAAmC;CACzD,MAAM,UAAU,GAAG,aAAa,YAAY,QAAQ;AAEpD,KAAI;EACF,MAAMA,SAAkB,KAAK,MAAM,QAAQ;AAE3C,MAAI,OAAO,WAAW,YAAY,WAAW,KAC3C,OAAM,IAAI,MAAM,wCAAwC,OAAO,SAAS;AAG1E,SAAO,eAAe,OAAkC;UACjD,OAAO;AACd,MAAI,iBAAiB,YACnB,OAAM,IAAI,MACR,mBAAmB,KAAK,SAAS,WAAW,CAAC,IAAI,MAAM,UACxD;AAEH,QAAM;;;;;;AAOV,eAAe,aAAa,YAA4C;AACtE,KAAI;EAIF,MAAM,UADU,MAAM,OADN,cAAc,WAAW,CAAC,OAEpB;AAEtB,MAAI,OAAO,WAAW,YAAY,WAAW,KAC3C,OAAM,IAAI,MAAM,wCAAwC,OAAO,SAAS;AAG1E,SAAO,eAAe,OAAkC;UACjD,OAAO;AACd,MAAI,iBAAiB,MACnB,OAAM,IAAI,MACR,kBAAkB,KAAK,SAAS,WAAW,CAAC,IAAI,MAAM,UACvD;AAEH,QAAM;;;AAIV,MAAMC,kBAAwC;CAC5C;CACA;CACA;CACD;AACD,MAAMC,kBAAuC,CAAC,UAAU,UAAU;;;;AAKlE,SAAS,eAAe,QAAgD;CACtE,MAAMC,SAAwB,EAAE;AAEhC,KAAI,eAAe,QAAQ;AACzB,MAAI,OAAO,OAAO,cAAc,YAAY,OAAO,YAAY,EAC7D,OAAM,IAAI,MACR,wDAAwD,OAAO,YAChE;AAEH,SAAO,YAAY,OAAO;;AAG5B,KAAI,YAAY,QAAQ;AACtB,MAAI,CAACC,gBAAc,SAAS,OAAO,OAAsB,CACvD,OAAM,IAAI,MACR,yCAAyCA,gBAAc,KAAK,KAAK,CAAC,QAAQ,OAAO,SAClF;AAEH,SAAO,SAAS,OAAO;;AAGzB,KAAI,YAAY,QAAQ;AACtB,MAAI,CAACC,gBAAc,SAAS,OAAO,OAAqB,CACtD,OAAM,IAAI,MACR,yCAAyCA,gBAAc,KAAK,KAAK,CAAC,QAAQ,OAAO,SAClF;AAEH,SAAO,SAAS,OAAO;;AAGzB,KAAI,cAAc,QAAQ;AACxB,MAAI,OAAO,OAAO,aAAa,YAAY,OAAO,aAAa,GAC7D,OAAM,IAAI,MACR,4DAA4D,OAAO,WACpE;AAEH,SAAO,WAAW,OAAO;;AAG3B,KAAI,eAAe,QAAQ;AACzB,MAAI,OAAO,OAAO,cAAc,YAAY,OAAO,cAAc,GAC/D,OAAM,IAAI,MACR,6DAA6D,OAAO,YACrE;AAEH,SAAO,YAAY,OAAO;;AAG5B,QAAO;;;;;;ACrIT,MAAaC,kBAAmC;CAC9C,WAAW;CACX,WAAW;CACX,QAAQ;CACR,QAAQ;CACR,UAAU;CACX;AAED,IAAa,qBAAb,cAAwC,MAAM;CAC5C,YAAY,SAAiB;AAC3B,QAAM,QAAQ;AACd,OAAK,OAAO;;;AAIhB,MAAMC,gBAAwC;CAC5C;CACA;CACA;CACD;AAED,MAAMC,gBAAuC,CAAC,UAAU,UAAU;;AAGlE,MAAMC,gBAAmC;CACvC;CACA;CACA;CACA;CACA;CACD;;;;;;;;AASD,SAAgB,gBAAgB,MAA+B;CAC7D,MAAMC,SAAwB,EAAE,UAAU,OAAO;AAEjD,MAAK,MAAM,OAAO,MAAM;AACtB,MAAI,QAAQ,UAAU;AACpB,UAAO,WAAW;AAClB;;AAGF,MAAI,IAAI,WAAW,SAAS,EAAE;GAC5B,MAAM,WAAW,IAAI,MAAM,EAAE;GAC7B,MAAM,QAAQ,OAAO,SAAS,UAAU,GAAG;AAE3C,OAAI,aAAa,MAAM,OAAO,MAAM,MAAM,CACxC,OAAM,IAAI,mBACR,6BAA6B,SAAS,sBACvC;AAEH,OAAI,QAAQ,EACV,OAAM,IAAI,mBAAmB,6BAA6B,QAAQ;AAGpE,UAAO,YAAY;aACV,IAAI,WAAW,YAAY,EAAE;GACtC,MAAM,QAAQ,IAAI,MAAM,EAAE;AAE1B,OAAI,CAAC,cAAc,SAAS,MAAqB,CAC/C,OAAM,IAAI,mBACR,gCAAgC,MAAM,mBAAmB,cAAc,KACrE,KACD,CAAC,GACH;AAGH,UAAO,SAAS;aACP,IAAI,WAAW,YAAY,EAAE;GACtC,MAAM,QAAQ,IAAI,MAAM,EAAE;AAE1B,OAAI,CAAC,cAAc,SAAS,MAAoB,CAC9C,OAAM,IAAI,mBACR,gCAAgC,MAAM,mBAAmB,cAAc,KACrE,KACD,CAAC,GACH;AAGH,UAAO,SAAS;aACP,IAAI,WAAW,cAAc,EAAE;GACxC,MAAM,QAAQ,IAAI,MAAM,GAAG;AAE3B,OAAI,UAAU,GACZ,OAAM,IAAI,mBACR,qDACD;AAGH,UAAO,WAAW;aACT,IAAI,WAAW,KAAK,EAAE;GAC/B,MAAM,aAAa,IAAI,MAAM,IAAI,CAAC;AAElC,OAAI,CAAC,cAAc,SAAS,WAAW,CACrC,OAAM,IAAI,mBAAmB,mBAAmB,aAAa;QAG/D,QAAO,YAAY;;AAIvB,QAAO;;;;;;;AAQT,SAAgB,kBAAkB,WAAyB;AACzD,KAAI,CAAC,GAAG,WAAW,UAAU,CAC3B,OAAM,IAAI,mBAAmB,6BAA6B,YAAY;AAIxE,KAAI,CADS,GAAG,SAAS,UAAU,CACzB,aAAa,CACrB,OAAM,IAAI,mBAAmB,4BAA4B,YAAY;;;;;;;AASzE,SAAgB,iBAAiB,cAA4B;AAC3D,KAAI,CAAC,GAAG,WAAW,aAAa,CAC9B,OAAM,IAAI,mBAAmB,uBAAuB,eAAe;;;;;AAOvE,SAAgB,iBAAyB;AACvC,QAAO;;;;;;;;;;;;;;;;;ACtKT,SAAS,KAAK,MAAsB;AAClC,QAAO,UAAU,KAAK;;AAGxB,SAAS,MAAM,MAAsB;AACnC,QAAO,WAAW,KAAK;;;;;;;;AASzB,SAAS,sBAAsB,OAAuB;CAEpD,MAAM,YAAY,MAAM,MAAM,qBAAqB;AACnD,KAAI,UACF,QAAO,UAAU;AAEnB,QAAO;;;;;AAqBT,SAAS,uBAAuB,WAA0C;CACxE,MAAM,2BAAW,IAAI,KAA8B;AAEnD,MAAK,MAAM,YAAY,WAAW;EAChC,MAAM,MAAM,GAAG,SAAS,iBAAiB,GAAG,SAAS;EAErD,IAAI,QAAQ,SAAS,IAAI,IAAI;AAC7B,MAAI,CAAC,OAAO;AACV,WAAQ;IACN,YAAY,SAAS;IACrB,kBAAkB,SAAS;IAC3B,YAAY,SAAS;IACrB,QAAQ,EAAE;IACX;AACD,YAAS,IAAI,KAAK,MAAM;;AAG1B,QAAM,OAAO,KAAK;GAChB,WAAW,SAAS;GACpB,OAAO,SAAS;GAChB,YAAY,SAAS,OAAO;GAC5B,QAAQ,SAAS;GAClB,CAAC;;AAGJ,QAAO,MAAM,KAAK,SAAS,QAAQ,CAAC;;;;;AAMtC,SAAS,uBAAuB,UAA4B;CAC1D,MAAM,QAAQ;EACZ;EACA,cAAc,SAAS,OAAO;EAC9B,GAAG,SAAS,KACT,OACC,WAAW,KAAK,MAAM,GAAG,KAAK,CAAC,CAAC,IAAI,KAAK,SACvC,QAAQ,KAAK,EACb,GAAG,eACJ,CAAC,GACL;EACD;EACD;AACD,SAAQ,IAAI,MAAM,KAAK,KAAK,CAAC;;;;;AAM/B,SAAS,uBAAuB,WAA6B;AAC3D,KAAI,UAAU,WAAW,EACvB;CAGF,MAAM,UAAU,uBAAuB,UAAU;AAEjD,MAAK,MAAM,SAAS,SAAS;EAC3B,MAAM,eAAe,KAAK,SAAS,QAAQ,KAAK,EAAE,MAAM,iBAAiB;EACzE,MAAM,aAAa,MAAM,OAAO,IAAI,cAAc;EAElD,MAAM,SAAS,MAAM,OAAO,IAAI,UAAU,EAAE;AAE5C,UAAQ,IACN,GAAG,KAAK,MAAM,MAAM,WAAW,CAAC,CAAC,GAAG,aAAa,GAAG,MAAM,aAC3D;AACD,UAAQ,IAAI,sBAAsB;AAElC,OAAK,MAAM,SAAS,MAAM,OACxB,SAAQ,IACN,OAAO,MAAM,UAAU,KAAK,sBAAsB,MAAM,MAAM,GAC/D;AAGH,UAAQ,IAAI,WAAW,WAAW,IAAI;AACtC,OAAK,MAAM,SAAS,QAAQ;GAC1B,MAAM,YAAY,KAAK,SAAS,QAAQ,KAAK,EAAE,MAAM,cAAc;AACnE,WAAQ,IAAI,OAAO,UAAU,GAAG,MAAM,YAAY;;AAGpD,UAAQ,IAAI,KAAK;;;;;;AAOrB,SAAS,gBAAgB,QAA8B;CACrD,MAAM,iBAAiB,OAAO,SAAS;CACvC,MAAM,yBAAyB,uBAC7B,OAAO,UACR,CAAC;AAEF,SAAQ,IAAI,MAAM;AAClB,SAAQ,IACN,SAAS,uBAAuB,8CAA8C,eAAe,eAC9F;;;;;AAMH,SAAgB,oBACd,QACA,MACM;AACN,KAAI,SAAS,UACX,wBAAuB,OAAO,SAAS;AAGzC,KAAI,OAAO,UAAU,WAAW,EAC9B,SAAQ,IAAI,gDAAgD;KAE5D,wBAAuB,OAAO,UAAU;AAG1C,iBAAgB,OAAO;;;;;;;;ACpJzB,SAAgB,0BACd,WACA,UAA4C,EAAE,EAChC;CACd,MAAM,EACJ,oBAAoB,uBACpB,mBAAmB,KAAK,KAAK,QAAQ,KAAK,EAAE,gBAAgB,KAC1D;CAGJ,MAAM,UAAU,IAAI,QAAQ;EAC1B;EACA,6BAA6B;EAC9B,CAAC;AAGF,SAAQ,sBAAsB,GAAG,UAAU,uBAAuB;AAQlE,QALuB,QAAQ,gBAAgB,CACH,QACzC,eAAe,CAAC,kBAAkB,WAAW,aAAa,CAAC,CAC7D;;;;;;;;ACbH,SAAS,cAAc,SAAwB;AAC7C,SAAQ,MAAM,UAAU,UAAU;AAClC,SAAQ,KAAK,EAAE;;AAGjB,eAAe,OAAsB;CAEnC,IAAIC;AACJ,KAAI;AACF,eAAa,gBAAgB,QAAQ,KAAK,MAAM,EAAE,CAAC;UAC5C,OAAO;AACd,MAAI,iBAAiB,mBACnB,eAAc,MAAM,QAAQ;AAE9B,QAAM;;AAGR,KAAI,WAAW,UAAU;AACvB,UAAQ,IAAI,gBAAgB,CAAC;AAC7B,UAAQ,KAAK,EAAE;;CAIjB,IAAIC;AACJ,KAAI;AACF,eAAa,MAAM,YAAY;UACxB,OAAO;AACd,MAAI,iBAAiB,MACnB,eAAc,MAAM,QAAQ;AAE9B,QAAM;;CAgBR,MAAM,EAAE,WAAW,WAAW,QAAQ,QAAQ,aAZb;EAC/B,WAAW,KAAK,QACd,WAAW,aAAa,WAAW,aAAa,gBAAgB,UACjE;EACD,WACE,WAAW,aAAa,WAAW,aAAa,gBAAgB;EAClE,QAAQ,WAAW,UAAU,WAAW,UAAU,gBAAgB;EAClE,QAAQ,WAAW,UAAU,WAAW,UAAU,gBAAgB;EAClE,UACE,WAAW,YAAY,WAAW,YAAY,gBAAgB;EACjE;AAKD,KAAI;AACF,oBAAkB,UAAU;UACrB,OAAO;AACd,MAAI,iBAAiB,mBACnB,eAAc,MAAM,QAAQ;AAE9B,QAAM;;AAIR,KAAI;AACF,mBAAiB,SAAS;UACnB,OAAO;AACd,MAAI,iBAAiB,mBACnB,eAAc,MAAM,QAAQ;AAE9B,QAAM;;AAGR,KAAI,WAAW,WAAW;AACxB,UAAQ,IAAI,qBAAqB,YAAY;AAC7C,UAAQ,IAAI,wBAAwB,YAAY;AAChD,UAAQ,IAAI,oBAAoB,OAAO,IAAI;;CAG7C,MAAM,uBAAuB,0BAA0B,WAAW,EAChE,kBAAkB,UACnB,CAAC;CAGF,MAAM,cAAc,iBAAiB,qBAAqB;CAG1D,MAAMC,cAA0C,EAAE;CAClD,MAAMC,eAA4C,EAAE;AAEpD,KAAI,WAAW,SAAS,WAAW,cAAc;EAC/C,MAAM,cAAc,iBAAiB,sBAAsB;GACzD;GACA;GACD,CAAC;AACF,cAAY,KAAK,GAAG,YAAY,SAAS;AACzC,eAAa,KAAK,GAAG,YAAY,UAAU;;AAG7C,KAAI,WAAW,SAAS,WAAW,aAAa;EAC9C,MAAM,kBAAkB,qBAAqB,sBAAsB;GACjE;GACA;GACD,CAAC;AACF,cAAY,KAAK,GAAG,gBAAgB,SAAS;AAC7C,eAAa,KAAK,GAAG,gBAAgB,UAAU;;AAQjD,qBAL+B;EAC7B,UAAU;EACV,WAAW;EACZ,EAE2B,OAAO;;AAGrC,MAAM,CAAC,OAAO,UAAU;AACtB,SAAQ,MAAM,MAAM;AACpB,SAAQ,KAAK,EAAE;EACf"}
|
|
1
|
+
{"version":3,"file":"cli.mjs","names":["config: unknown","VALID_TARGETS: readonly AnalyzeMode[]","VALID_OUTPUTS: readonly OutputMode[]","result: DittoryConfig","VALID_TARGETS","VALID_OUTPUTS","DEFAULT_OPTIONS: ResolvedOptions","VALID_TARGETS: readonly AnalyzeMode[]","VALID_OUTPUTS: readonly OutputMode[]","VALID_OPTIONS: readonly string[]","result: RawCliOptions","cliOptions: ReturnType<typeof parseCliOptions>","fileConfig: Awaited<ReturnType<typeof loadConfig>>","allExported: AnalysisResult[\"exported\"]","allConstants: AnalysisResult[\"constants\"]"],"sources":["../src/cli/loadConfig.ts","../src/cli/parseCliOptions.ts","../src/output/printAnalysisResult.ts","../src/source/createFilteredSourceFiles.ts","../src/cli.ts"],"sourcesContent":["import fs from \"node:fs\";\nimport path from \"node:path\";\nimport { pathToFileURL } from \"node:url\";\nimport { VALID_VALUE_TYPES, type ValueType } from \"@/types\";\nimport type { AnalyzeMode, OutputMode } from \"./parseCliOptions\";\n\n/** コンフィグファイルの検索順序 */\nconst CONFIG_FILE_NAMES = [\n \"dittory.config.js\",\n \"dittory.config.mjs\",\n \"dittory.config.json\",\n] as const;\n\n/**\n * コンフィグファイルの設定項目\n */\nexport interface DittoryConfig {\n minUsages?: number;\n target?: AnalyzeMode;\n output?: OutputMode;\n tsconfig?: string;\n targetDir?: string;\n valueTypes?: ValueType[] | \"all\";\n}\n\n/**\n * コンフィグファイルを読み込む\n *\n * 現在の作業ディレクトリから以下の順序でコンフィグファイルを探す:\n * 1. dittory.config.js\n * 2. dittory.config.mjs\n * 3. dittory.config.json\n *\n * ファイルが存在しない場合は空のオブジェクトを返す。\n *\n * @returns コンフィグオブジェクト\n * @throws {Error} コンフィグファイルの読み込みに失敗した場合\n */\nexport async function loadConfig(): Promise<DittoryConfig> {\n const cwd = process.cwd();\n\n for (const fileName of CONFIG_FILE_NAMES) {\n const configPath = path.join(cwd, fileName);\n\n if (!fs.existsSync(configPath)) {\n continue;\n }\n\n if (fileName.endsWith(\".json\")) {\n return loadJsonConfig(configPath);\n }\n return loadJsConfig(configPath);\n }\n\n return {};\n}\n\n/**\n * JSON コンフィグを読み込む\n */\nfunction loadJsonConfig(configPath: string): DittoryConfig {\n const content = fs.readFileSync(configPath, \"utf-8\");\n\n try {\n const config: unknown = JSON.parse(content);\n\n if (typeof config !== \"object\" || config === null) {\n throw new Error(`Invalid config: expected object, got ${typeof config}`);\n }\n\n return validateConfig(config as Record<string, unknown>);\n } catch (error) {\n if (error instanceof SyntaxError) {\n throw new Error(\n `Failed to parse ${path.basename(configPath)}: ${error.message}`,\n );\n }\n throw error;\n }\n}\n\n/**\n * JS コンフィグを読み込む\n */\nasync function loadJsConfig(configPath: string): Promise<DittoryConfig> {\n try {\n // Windows 対応のため file:// URL に変換\n const fileUrl = pathToFileURL(configPath).href;\n const module = (await import(fileUrl)) as { default?: unknown };\n const config = module.default;\n\n if (typeof config !== \"object\" || config === null) {\n throw new Error(`Invalid config: expected object, got ${typeof config}`);\n }\n\n return validateConfig(config as Record<string, unknown>);\n } catch (error) {\n if (error instanceof Error) {\n throw new Error(\n `Failed to load ${path.basename(configPath)}: ${error.message}`,\n );\n }\n throw error;\n }\n}\n\nconst VALID_TARGETS: readonly AnalyzeMode[] = [\n \"all\",\n \"components\",\n \"functions\",\n];\nconst VALID_OUTPUTS: readonly OutputMode[] = [\"simple\", \"verbose\"];\n\n/**\n * コンフィグの値を検証する\n */\nfunction validateConfig(config: Record<string, unknown>): DittoryConfig {\n const result: DittoryConfig = {};\n\n if (\"minUsages\" in config) {\n if (typeof config.minUsages !== \"number\" || config.minUsages < 1) {\n throw new Error(\n `Invalid config: minUsages must be a number >= 1, got ${config.minUsages}`,\n );\n }\n result.minUsages = config.minUsages;\n }\n\n if (\"target\" in config) {\n if (!VALID_TARGETS.includes(config.target as AnalyzeMode)) {\n throw new Error(\n `Invalid config: target must be one of ${VALID_TARGETS.join(\", \")}, got ${config.target}`,\n );\n }\n result.target = config.target as AnalyzeMode;\n }\n\n if (\"output\" in config) {\n if (!VALID_OUTPUTS.includes(config.output as OutputMode)) {\n throw new Error(\n `Invalid config: output must be one of ${VALID_OUTPUTS.join(\", \")}, got ${config.output}`,\n );\n }\n result.output = config.output as OutputMode;\n }\n\n if (\"tsconfig\" in config) {\n if (typeof config.tsconfig !== \"string\" || config.tsconfig === \"\") {\n throw new Error(\n `Invalid config: tsconfig must be a non-empty string, got ${config.tsconfig}`,\n );\n }\n result.tsconfig = config.tsconfig;\n }\n\n if (\"targetDir\" in config) {\n if (typeof config.targetDir !== \"string\" || config.targetDir === \"\") {\n throw new Error(\n `Invalid config: targetDir must be a non-empty string, got ${config.targetDir}`,\n );\n }\n result.targetDir = config.targetDir;\n }\n\n if (\"valueTypes\" in config) {\n const value = config.valueTypes;\n\n if (value === \"all\") {\n result.valueTypes = \"all\";\n } else if (Array.isArray(value)) {\n for (const type of value) {\n if (!VALID_VALUE_TYPES.includes(type as ValueType)) {\n throw new Error(\n `Invalid config: valueTypes contains invalid type \"${type}\" (valid values: ${VALID_VALUE_TYPES.join(\", \")}, all)`,\n );\n }\n }\n result.valueTypes = value as ValueType[];\n } else {\n throw new Error(\n `Invalid config: valueTypes must be \"all\" or an array of value types, got ${value}`,\n );\n }\n }\n\n return result;\n}\n","import fs from \"node:fs\";\nimport { VALID_VALUE_TYPES, type ValueType } from \"@/types\";\n\nexport type AnalyzeMode = \"all\" | \"components\" | \"functions\";\nexport type OutputMode = \"simple\" | \"verbose\";\n\n/**\n * CLI で明示的に指定されたオプション(デフォルト値なし)\n */\nexport interface RawCliOptions {\n targetDir?: string;\n minUsages?: number;\n target?: AnalyzeMode;\n output?: OutputMode;\n tsconfig?: string;\n valueTypes?: ValueType[];\n showHelp: boolean;\n}\n\n/**\n * 解決済みのオプション(デフォルト値適用後)\n */\nexport interface ResolvedOptions {\n targetDir: string;\n minUsages: number;\n target: AnalyzeMode;\n output: OutputMode;\n tsconfig: string;\n valueTypes: ValueType[] | \"all\";\n}\n\n/** デフォルトのオプション値 */\nexport const DEFAULT_OPTIONS: ResolvedOptions = {\n targetDir: \"./src\",\n minUsages: 2,\n target: \"all\",\n output: \"simple\",\n tsconfig: \"./tsconfig.json\",\n valueTypes: \"all\",\n};\n\nexport class CliValidationError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"CliValidationError\";\n }\n}\n\nconst VALID_TARGETS: readonly AnalyzeMode[] = [\n \"all\",\n \"components\",\n \"functions\",\n];\n\nconst VALID_OUTPUTS: readonly OutputMode[] = [\"simple\", \"verbose\"];\n\n/** 不明なオプションの検出に使用 */\nconst VALID_OPTIONS: readonly string[] = [\n \"--min\",\n \"--target\",\n \"--output\",\n \"--tsconfig\",\n \"--value-types\",\n \"--help\",\n];\n\n/**\n * CLIオプションをパースする\n *\n * 明示的に指定されたオプションのみを返す(デフォルト値は含まない)\n *\n * @throws {CliValidationError} オプションが無効な場合\n */\nexport function parseCliOptions(args: string[]): RawCliOptions {\n const result: RawCliOptions = { showHelp: false };\n\n for (const arg of args) {\n if (arg === \"--help\") {\n result.showHelp = true;\n continue;\n }\n\n if (arg.startsWith(\"--min=\")) {\n const valueStr = arg.slice(6);\n const value = Number.parseInt(valueStr, 10);\n\n if (valueStr === \"\" || Number.isNaN(value)) {\n throw new CliValidationError(\n `Invalid value for --min: \"${valueStr}\" (must be a number)`,\n );\n }\n if (value < 1) {\n throw new CliValidationError(`--min must be at least 1: ${value}`);\n }\n\n result.minUsages = value;\n } else if (arg.startsWith(\"--target=\")) {\n const value = arg.slice(9);\n\n if (!VALID_TARGETS.includes(value as AnalyzeMode)) {\n throw new CliValidationError(\n `Invalid value for --target: \"${value}\" (valid values: ${VALID_TARGETS.join(\n \", \",\n )})`,\n );\n }\n\n result.target = value as AnalyzeMode;\n } else if (arg.startsWith(\"--output=\")) {\n const value = arg.slice(9);\n\n if (!VALID_OUTPUTS.includes(value as OutputMode)) {\n throw new CliValidationError(\n `Invalid value for --output: \"${value}\" (valid values: ${VALID_OUTPUTS.join(\n \", \",\n )})`,\n );\n }\n\n result.output = value as OutputMode;\n } else if (arg.startsWith(\"--tsconfig=\")) {\n const value = arg.slice(11);\n\n if (value === \"\") {\n throw new CliValidationError(\n \"Invalid value for --tsconfig: path cannot be empty\",\n );\n }\n\n result.tsconfig = value;\n } else if (arg.startsWith(\"--value-types=\")) {\n const value = arg.slice(14);\n\n if (value === \"\") {\n throw new CliValidationError(\n \"Invalid value for --value-types: value cannot be empty\",\n );\n }\n\n // \"all\" の場合はデフォルト扱い(RawCliOptionsには含めない)\n if (value === \"all\") {\n continue;\n }\n\n const types = value.split(\",\").map((t) => t.trim());\n for (const type of types) {\n if (!VALID_VALUE_TYPES.includes(type as ValueType)) {\n throw new CliValidationError(\n `Invalid value type: \"${type}\" (valid values: ${VALID_VALUE_TYPES.join(\", \")}, all)`,\n );\n }\n }\n\n result.valueTypes = types as ValueType[];\n } else if (arg.startsWith(\"--\")) {\n const optionName = arg.split(\"=\")[0];\n\n if (!VALID_OPTIONS.includes(optionName)) {\n throw new CliValidationError(`Unknown option: ${optionName}`);\n }\n } else {\n result.targetDir = arg;\n }\n }\n\n return result;\n}\n\n/**\n * 対象ディレクトリの存在を検証する\n *\n * @throws {CliValidationError} ディレクトリが存在しない、またはディレクトリでない場合\n */\nexport function validateTargetDir(targetDir: string): void {\n if (!fs.existsSync(targetDir)) {\n throw new CliValidationError(`Directory does not exist: ${targetDir}`);\n }\n\n const stat = fs.statSync(targetDir);\n if (!stat.isDirectory()) {\n throw new CliValidationError(`Path is not a directory: ${targetDir}`);\n }\n}\n\n/**\n * tsconfig.json の存在を検証する\n *\n * @throws {CliValidationError} ファイルが存在しない場合\n */\nexport function validateTsConfig(tsConfigPath: string): void {\n if (!fs.existsSync(tsConfigPath)) {\n throw new CliValidationError(`tsconfig not found: ${tsConfigPath}`);\n }\n}\n\n/**\n * ヘルプメッセージを取得する\n */\nexport function getHelpMessage(): string {\n return `\nUsage: dittory [options] [directory]\n\nOptions:\n --min=<number> Minimum usage count (default: 2)\n --target=<mode> Analysis target: all, components, functions (default: all)\n --output=<mode> Output mode: simple, verbose (default: simple)\n --tsconfig=<path> Path to tsconfig.json (default: ./tsconfig.json)\n --value-types=<types> Value types to detect: boolean, number, string, enum, undefined, all (default: all)\n Multiple types can be specified with comma: --value-types=boolean,string\n --help Show this help message\n\nArguments:\n directory Target directory to analyze (default: ./src)\n`;\n}\n","import path from \"node:path\";\nimport type { OutputMode } from \"@/cli/parseCliOptions\";\nimport type { AnalysisResult, Constant, Exported } from \"@/types\";\n\nfunction bold(text: string): string {\n return `\\x1b[1m${text}\\x1b[0m`;\n}\n\nfunction green(text: string): string {\n return `\\x1b[32m${text}\\x1b[0m`;\n}\n\n/**\n * 値を表示用にフォーマットする\n *\n * 内部的にはenum区別のためにファイルパスを含むが、表示時は不要なので除去する\n * 例: \"/path/to/file.ts:ButtonVariant.Primary=\\\"primary\\\"\" → \"ButtonVariant.Primary\"\n */\nfunction formatValueForDisplay(value: string): string {\n // enum形式: \"ファイルパス:EnumName.MemberName=値\" のパターンをチェック\n const enumMatch = value.match(/^.+:(\\w+\\.\\w+)=.+$/);\n if (enumMatch) {\n return enumMatch[1];\n }\n return value;\n}\n\n/**\n * グループ化された定数情報\n */\ninterface GroupedConstant {\n targetName: string;\n targetSourceFile: string;\n targetLine: number;\n params: Array<{\n paramName: string;\n value: string;\n usageCount: number;\n usages: Constant[\"usages\"];\n }>;\n}\n\n/**\n * Constant[]を関数/コンポーネント単位でグループ化する\n */\nfunction groupConstantsByTarget(constants: Constant[]): GroupedConstant[] {\n const groupMap = new Map<string, GroupedConstant>();\n\n for (const constant of constants) {\n const key = `${constant.targetSourceFile}:${constant.targetName}`;\n\n let group = groupMap.get(key);\n if (!group) {\n group = {\n targetName: constant.targetName,\n targetSourceFile: constant.targetSourceFile,\n targetLine: constant.targetLine,\n params: [],\n };\n groupMap.set(key, group);\n }\n\n group.params.push({\n paramName: constant.paramName,\n value: constant.value,\n usageCount: constant.usages.length,\n usages: constant.usages,\n });\n }\n\n return Array.from(groupMap.values());\n}\n\n/**\n * exportされた関数の一覧を出力\n */\nfunction printExportedFunctions(exported: Exported[]): void {\n const lines = [\n \"Collecting exported functions...\",\n ` → Found ${exported.length} function(s)`,\n ...exported.map(\n (fn) =>\n ` - ${bold(green(fn.name))} (${path.relative(\n process.cwd(),\n fn.sourceFilePath,\n )})`,\n ),\n \"\",\n ];\n console.log(lines.join(\"\\n\"));\n}\n\n/**\n * 常に同じ値が渡されている引数を出力\n */\nfunction printConstantArguments(constants: Constant[]): void {\n if (constants.length === 0) {\n return;\n }\n\n const grouped = groupConstantsByTarget(constants);\n\n for (const group of grouped) {\n const relativePath = path.relative(process.cwd(), group.targetSourceFile);\n const usageCount = group.params[0]?.usageCount ?? 0;\n // 使用箇所は全パラメータで同じなので、最初のパラメータから取得\n const usages = group.params[0]?.usages ?? [];\n\n console.log(\n `${bold(green(group.targetName))} ${relativePath}:${group.targetLine}`,\n );\n console.log(\"Constant Arguments:\");\n\n for (const param of group.params) {\n console.log(\n ` - ${param.paramName} = ${formatValueForDisplay(param.value)}`,\n );\n }\n\n console.log(`Usages (${usageCount}):`);\n for (const usage of usages) {\n const usagePath = path.relative(process.cwd(), usage.usageFilePath);\n console.log(` - ${usagePath}:${usage.usageLine}`);\n }\n\n console.log(\"\\n\");\n }\n}\n\n/**\n * 統計情報を出力\n */\nfunction printStatistics(result: AnalysisResult): void {\n const totalFunctions = result.exported.length;\n const functionsWithConstants = groupConstantsByTarget(\n result.constants,\n ).length;\n\n console.log(\"---\");\n console.log(\n `Found ${functionsWithConstants} function(s) with constant arguments out of ${totalFunctions} function(s).`,\n );\n}\n\n/**\n * 解析結果を出力\n */\nexport function printAnalysisResult(\n result: AnalysisResult,\n mode: OutputMode,\n): void {\n if (mode === \"verbose\") {\n printExportedFunctions(result.exported);\n }\n\n if (result.constants.length === 0) {\n console.log(\"No arguments with constant values were found.\");\n } else {\n printConstantArguments(result.constants);\n }\n\n printStatistics(result);\n}\n","import path from \"node:path\";\nimport { Project, type SourceFile } from \"ts-morph\";\nimport { isTestOrStorybookFile } from \"@/source/fileFilters\";\nimport type { FileFilter } from \"@/types\";\n\nexport interface CreateFilteredSourceFilesOptions {\n shouldExcludeFile?: FileFilter;\n tsConfigFilePath?: string;\n}\n\n/**\n * プロジェクトを初期化し、フィルタリングされたソースファイルを取得する\n */\nexport function createFilteredSourceFiles(\n targetDir: string,\n options: CreateFilteredSourceFilesOptions = {},\n): SourceFile[] {\n const {\n shouldExcludeFile = isTestOrStorybookFile,\n tsConfigFilePath = path.join(process.cwd(), \"tsconfig.json\"),\n } = options;\n\n // プロジェクトを初期化\n const project = new Project({\n tsConfigFilePath,\n skipAddingFilesFromTsConfig: true,\n });\n\n // 対象ディレクトリのファイルを追加\n project.addSourceFilesAtPaths(`${targetDir}/**/*.{ts,tsx,js,jsx}`);\n\n // ファイルをフィルタリング\n const allSourceFiles = project.getSourceFiles();\n const sourceFilesToAnalyze = allSourceFiles.filter(\n (sourceFile) => !shouldExcludeFile(sourceFile.getFilePath()),\n );\n\n return sourceFilesToAnalyze;\n}\n","#!/usr/bin/env node\nimport path from \"node:path\";\nimport { analyzeFunctionsCore } from \"@/analyzeFunctions\";\nimport { analyzePropsCore } from \"@/analyzeProps\";\nimport { loadConfig } from \"@/cli/loadConfig\";\nimport {\n CliValidationError,\n DEFAULT_OPTIONS,\n getHelpMessage,\n parseCliOptions,\n type ResolvedOptions,\n validateTargetDir,\n validateTsConfig,\n} from \"@/cli/parseCliOptions\";\nimport { collectCallSites } from \"@/extraction/callSiteCollector\";\nimport { printAnalysisResult } from \"@/output/printAnalysisResult\";\nimport { createFilteredSourceFiles } from \"@/source/createFilteredSourceFiles\";\nimport type { AnalysisResult } from \"@/types\";\n\n/**\n * エラーメッセージを表示してプロセスを終了する\n */\nfunction exitWithError(message: string): never {\n console.error(`Error: ${message}`);\n process.exit(1);\n}\n\nasync function main(): Promise<void> {\n // CLI オプションをパース\n let cliOptions: ReturnType<typeof parseCliOptions>;\n try {\n cliOptions = parseCliOptions(process.argv.slice(2));\n } catch (error) {\n if (error instanceof CliValidationError) {\n exitWithError(error.message);\n }\n throw error;\n }\n\n if (cliOptions.showHelp) {\n console.log(getHelpMessage());\n process.exit(0);\n }\n\n // コンフィグファイルを読み込む\n let fileConfig: Awaited<ReturnType<typeof loadConfig>>;\n try {\n fileConfig = await loadConfig();\n } catch (error) {\n if (error instanceof Error) {\n exitWithError(error.message);\n }\n throw error;\n }\n\n // オプションをマージ: CLI > コンフィグ > デフォルト\n const options: ResolvedOptions = {\n targetDir: path.resolve(\n cliOptions.targetDir ?? fileConfig.targetDir ?? DEFAULT_OPTIONS.targetDir,\n ),\n minUsages:\n cliOptions.minUsages ?? fileConfig.minUsages ?? DEFAULT_OPTIONS.minUsages,\n target: cliOptions.target ?? fileConfig.target ?? DEFAULT_OPTIONS.target,\n output: cliOptions.output ?? fileConfig.output ?? DEFAULT_OPTIONS.output,\n tsconfig:\n cliOptions.tsconfig ?? fileConfig.tsconfig ?? DEFAULT_OPTIONS.tsconfig,\n valueTypes:\n cliOptions.valueTypes ??\n fileConfig.valueTypes ??\n DEFAULT_OPTIONS.valueTypes,\n };\n\n const { targetDir, minUsages, target, output, tsconfig, valueTypes } =\n options;\n\n // 対象ディレクトリの存在を検証\n try {\n validateTargetDir(targetDir);\n } catch (error) {\n if (error instanceof CliValidationError) {\n exitWithError(error.message);\n }\n throw error;\n }\n\n // tsconfig.json の存在を検証\n try {\n validateTsConfig(tsconfig);\n } catch (error) {\n if (error instanceof CliValidationError) {\n exitWithError(error.message);\n }\n throw error;\n }\n\n if (output === \"verbose\") {\n console.log(`Target directory: ${targetDir}`);\n console.log(`Minimum usage count: ${minUsages}`);\n console.log(`Analysis target: ${target}\\n`);\n }\n\n const sourceFilesToAnalyze = createFilteredSourceFiles(targetDir, {\n tsConfigFilePath: tsconfig,\n });\n\n // 呼び出し情報を事前収集(パラメータ経由で渡された値を解決するために使用)\n const callSiteMap = collectCallSites(sourceFilesToAnalyze);\n\n // 各解析結果を収集\n const allExported: AnalysisResult[\"exported\"] = [];\n const allConstants: AnalysisResult[\"constants\"] = [];\n\n if (target === \"all\" || target === \"components\") {\n const propsResult = analyzePropsCore(sourceFilesToAnalyze, {\n minUsages,\n valueTypes,\n callSiteMap,\n });\n allExported.push(...propsResult.exported);\n allConstants.push(...propsResult.constants);\n }\n\n if (target === \"all\" || target === \"functions\") {\n const functionsResult = analyzeFunctionsCore(sourceFilesToAnalyze, {\n minUsages,\n valueTypes,\n callSiteMap,\n });\n allExported.push(...functionsResult.exported);\n allConstants.push(...functionsResult.constants);\n }\n\n const result: AnalysisResult = {\n exported: allExported,\n constants: allConstants,\n };\n\n printAnalysisResult(result, output);\n}\n\nmain().catch((error) => {\n console.error(error);\n process.exit(1);\n});\n"],"mappings":"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"}
|
package/dist/index.d.mts
CHANGED
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|
import { FunctionDeclaration, MethodDeclaration, Node, SourceFile, VariableDeclaration } from "ts-morph";
|
|
2
2
|
|
|
3
|
+
//#region src/utils/valueTypeDetector.d.ts
|
|
4
|
+
/**
|
|
5
|
+
* フィルタリング対象の値種別
|
|
6
|
+
*/
|
|
7
|
+
type ValueType = "boolean" | "number" | "string" | "enum" | "undefined";
|
|
8
|
+
//#endregion
|
|
3
9
|
//#region src/types.d.ts
|
|
4
|
-
|
|
5
10
|
/**
|
|
6
11
|
* ファイルパスを受け取り、除外すべきかどうかを判定する関数の型
|
|
7
12
|
*/
|
|
@@ -120,6 +125,8 @@ declare function collectCallSites(sourceFiles: SourceFile[], shouldExcludeFile?:
|
|
|
120
125
|
interface AnalyzeFunctionsOptions {
|
|
121
126
|
shouldExcludeFile?: FileFilter;
|
|
122
127
|
minUsages?: number;
|
|
128
|
+
/** 検出対象の値種別。デフォルト: "all" */
|
|
129
|
+
valueTypes?: ValueType[] | "all";
|
|
123
130
|
/** 呼び出し情報(パラメータ経由で渡された値を解決するために使用) */
|
|
124
131
|
callSiteMap: CallSiteMap;
|
|
125
132
|
}
|
|
@@ -141,6 +148,8 @@ declare function analyzeFunctionsCore(sourceFiles: SourceFile[], options: Analyz
|
|
|
141
148
|
interface AnalyzePropsOptions {
|
|
142
149
|
shouldExcludeFile?: FileFilter;
|
|
143
150
|
minUsages?: number;
|
|
151
|
+
/** 検出対象の値種別。デフォルト: "all" */
|
|
152
|
+
valueTypes?: ValueType[] | "all";
|
|
144
153
|
/** 呼び出し情報(パラメータ経由で渡された値を解決するために使用) */
|
|
145
154
|
callSiteMap: CallSiteMap;
|
|
146
155
|
}
|
|
@@ -172,6 +181,7 @@ interface DittoryConfig {
|
|
|
172
181
|
output?: OutputMode;
|
|
173
182
|
tsconfig?: string;
|
|
174
183
|
targetDir?: string;
|
|
184
|
+
valueTypes?: ValueType[] | "all";
|
|
175
185
|
}
|
|
176
186
|
//#endregion
|
|
177
187
|
export { type AnalysisResult, type AnalyzeMode, type CallSiteMap, type Constant, type Definition, type DittoryConfig, type Exported, type FileFilter, type OutputMode, type Usage, analyzeFunctionsCore, analyzePropsCore, collectCallSites };
|
package/dist/index.mjs
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { i as collectCallSites, n as analyzeFunctionsCore, t as analyzePropsCore } from "./analyzeProps-6k4QTasK.mjs";
|
|
2
2
|
|
|
3
3
|
export { analyzeFunctionsCore, analyzePropsCore, collectCallSites };
|
package/package.json
CHANGED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"analyzeProps-B0TjCZhP.mjs","names":["current: Node | undefined","resolvedValue: ArgValue | undefined","value: ArgValue","callSiteMap: CallSiteMap","paramNames: string[]","current: Node | undefined","usages: Usage[]","groupedMap: GroupedMap","result: Constant[]","results: Exported[]","callable: Exported","groupedUsages: Record<string, Usage[]>","results: Exported[]","callable: Exported","groupedUsages: Record<string, Usage[]>","results: ClassifiedDeclaration[]","type: DeclarationType","propsParam: Node | undefined","exportedComponents: Exported[]","component: Exported","groupedUsages: Record<string, Usage[]>"],"sources":["../src/source/fileFilters.ts","../src/extraction/parameterUtils.ts","../src/extraction/callSiteCollector.ts","../src/extraction/resolveExpressionValue.ts","../src/extraction/flattenObjectExpression.ts","../src/extraction/hasDisableComment.ts","../src/extraction/extractUsages.ts","../src/utils/getSingleValueFromSet.ts","../src/analyzer/baseAnalyzer.ts","../src/analyzer/classMethodAnalyzer.ts","../src/analyzer/functionAnalyzer.ts","../src/components/isReactComponent.ts","../src/source/classifyDeclarations.ts","../src/analyzeFunctions.ts","../src/components/getProps.ts","../src/analyzer/componentAnalyzer.ts","../src/analyzeProps.ts"],"sourcesContent":["/**\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 { Node, SyntaxKind } from \"ts-morph\";\nimport {\n type ArgValue,\n ArgValueType,\n type CallSiteMap,\n} from \"./callSiteCollector\";\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 decl = symbol?.getDeclarations()[0];\n if (!decl) return false;\n const kind = decl.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 */\nexport function 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 */\nexport function 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 */\nexport function 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 { type: ArgValueType.Literal, value: expression.getText() };\n }\n\n const functionName = getFunctionName(functionScope);\n const path = buildParameterPath(expression);\n\n return { type: ArgValueType.ParamRef, filePath, functionName, path };\n}\n\n/**\n * ArgValue を比較可能な文字列キーに変換\n * 同じ値かどうかの判定に使用\n */\nexport function argValueToKey(value: ArgValue): string {\n switch (value.type) {\n case ArgValueType.Literal:\n return `literal:${value.value}`;\n case ArgValueType.Function:\n return `function:${value.filePath}:${value.line}`;\n case ArgValueType.ParamRef:\n return `paramRef:${value.filePath}:${value.functionName}:${value.path}`;\n case ArgValueType.Undefined:\n return \"undefined\";\n }\n}\n\n/**\n * パラメータ参照を解決する\n * callSiteMapを使って、パラメータに渡されたすべての値を取得し、\n * すべて同じ値ならその値を返す。異なる値があればundefinedを返す。\n *\n * @param argValue - 解決対象の ArgValue\n * @param callSiteMap - 呼び出し情報マップ\n * @param visited - 循環参照防止用のセット\n * @returns 解決された ArgValue。異なる値がある場合や解決できない場合はundefined\n */\nexport function resolveParameterValue(\n argValue: ArgValue,\n callSiteMap: CallSiteMap,\n visited: Set<string> = new Set(),\n): ArgValue | undefined {\n // パラメータ参照でない場合はそのまま返す\n if (argValue.type !== ArgValueType.ParamRef) {\n return argValue;\n }\n\n // 循環参照を防ぐ\n const key = argValueToKey(argValue);\n if (visited.has(key)) {\n return undefined;\n }\n visited.add(key);\n\n const { filePath, functionName, path } = argValue;\n const targetId = `${filePath}:${functionName}`;\n const callSiteInfo = callSiteMap.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 const resolved = resolveParameterValue(\n arg.value,\n callSiteMap,\n new Set(visited),\n );\n if (resolved === undefined) {\n return undefined;\n }\n\n const resolvedKey = argValueToKey(resolved);\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","import {\n type CallExpression,\n type JsxOpeningElement,\n type JsxSelfClosingElement,\n Node,\n type SourceFile,\n SyntaxKind,\n} from \"ts-morph\";\nimport { isTestOrStorybookFile } from \"@/source/fileFilters\";\nimport type { FileFilter } from \"@/types\";\nimport { createParamRefValue, isParameterReference } from \"./parameterUtils\";\n\n/**\n * ArgValueのtype識別子\n */\nexport const ArgValueType = {\n Literal: \"literal\",\n Function: \"function\",\n ParamRef: \"paramRef\",\n Undefined: \"undefined\",\n} as const;\n\n/**\n * 引数の値を表す union 型\n * 文字列エンコーディングの代わりに型安全な表現を使用\n */\nexport type ArgValue =\n | { type: typeof ArgValueType.Literal; value: string }\n | { type: typeof ArgValueType.Function; filePath: string; line: number }\n | {\n type: typeof ArgValueType.ParamRef;\n filePath: string;\n functionName: string;\n path: string;\n }\n | { type: typeof ArgValueType.Undefined };\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 type CallSiteInfo = Map<string, CallSiteArg[]>;\n\n/**\n * すべての関数/コンポーネントの呼び出し情報\n * key: \"ファイルパス:関数名\" 形式の識別子\n */\nexport type CallSiteMap = Map<string, CallSiteInfo>;\n\n/**\n * 関数/コンポーネントの識別子を生成する\n */\nexport function createTargetId(filePath: string, name: string): string {\n return `${filePath}:${name}`;\n}\n\n/**\n * 識別子から関数/コンポーネント名を抽出する\n */\nexport function parseTargetId(targetId: string): {\n filePath: string;\n name: string;\n} {\n const lastColonIndex = targetId.lastIndexOf(\":\");\n return {\n filePath: targetId.substring(0, lastColonIndex),\n name: targetId.substring(lastColonIndex + 1),\n };\n}\n\n/**\n * 式から ArgValue を抽出する\n *\n * 式の値を型安全な ArgValue として返す。\n * 呼び出し情報収集時および式の値解決時に使用する。\n *\n * @param expression - 解析対象の式ノード\n * @returns 式の値を表す ArgValue\n */\nexport function extractArgValue(expression: Node): ArgValue {\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 {\n type: ArgValueType.Function,\n filePath: sourceFile.getFilePath(),\n line,\n };\n }\n\n // PropertyAccessExpression (例: Status.Active, props.number)\n if (Node.isPropertyAccessExpression(expression)) {\n const symbol = expression.getSymbol();\n const decl = symbol?.getDeclarations()[0];\n\n // enum memberの場合\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 return {\n type: ArgValueType.Literal,\n value: `${filePath}:${enumName}.${memberName}=${JSON.stringify(value)}`,\n };\n }\n }\n\n // パラメータのプロパティアクセスの場合\n if (isParameterReference(expression.getExpression())) {\n return createParamRefValue(expression);\n }\n }\n\n // Identifier (変数参照)\n if (Node.isIdentifier(expression)) {\n if (expression.getText() === \"undefined\") {\n return { type: ArgValueType.Undefined };\n }\n\n const symbol = expression.getSymbol();\n const resolvedSymbol = symbol?.getAliasedSymbol() ?? symbol;\n const decl = resolvedSymbol?.getDeclarations()[0];\n\n if (decl) {\n const kind = decl.getKind();\n // パラメータまたはBindingElementの場合\n if (kind === SyntaxKind.Parameter || kind === SyntaxKind.BindingElement) {\n return createParamRefValue(expression);\n }\n\n // 変数宣言の場合は初期化子を再帰的に解決\n if (Node.isVariableDeclaration(decl)) {\n const initializer = decl.getInitializer();\n if (initializer) {\n return extractArgValue(initializer);\n }\n return {\n type: ArgValueType.Literal,\n value: `${decl.getSourceFile().getFilePath()}:${expression.getText()}`,\n };\n }\n\n // その他の宣言タイプ(インポート宣言など)\n // ファイルパス + 変数名で識別する\n return {\n type: ArgValueType.Literal,\n value: `${decl.getSourceFile().getFilePath()}:${expression.getText()}`,\n };\n }\n }\n\n // リテラル型\n if (type.isStringLiteral() || type.isNumberLiteral()) {\n return {\n type: ArgValueType.Literal,\n value: JSON.stringify(type.getLiteralValue()),\n };\n }\n\n if (type.isBooleanLiteral()) {\n return { type: ArgValueType.Literal, value: type.getText() };\n }\n\n return { type: ArgValueType.Literal, value: expression.getText() };\n}\n\n/**\n * JSX要素から呼び出し情報を抽出\n */\nfunction extractFromJsxElement(\n element: JsxOpeningElement | JsxSelfClosingElement,\n targetId: string,\n callSiteMap: CallSiteMap,\n): void {\n const sourceFile = element.getSourceFile();\n const filePath = sourceFile.getFilePath();\n\n let info = callSiteMap.get(targetId);\n if (!info) {\n info = new Map();\n callSiteMap.set(targetId, info);\n }\n\n for (const attr of element.getAttributes()) {\n if (!Node.isJsxAttribute(attr)) continue;\n\n const propName = attr.getNameNode().getText();\n const initializer = attr.getInitializer();\n\n let value: ArgValue;\n if (!initializer) {\n // boolean shorthand\n value = { type: ArgValueType.Literal, value: \"true\" };\n } else if (Node.isJsxExpression(initializer)) {\n const expr = initializer.getExpression();\n value = expr ? extractArgValue(expr) : { type: ArgValueType.Undefined };\n } else {\n value = { type: ArgValueType.Literal, value: initializer.getText() };\n }\n\n const args = info.get(propName) ?? [];\n args.push({\n name: propName,\n value,\n filePath,\n line: element.getStartLineNumber(),\n });\n info.set(propName, args);\n }\n}\n\n/**\n * 関数呼び出しから呼び出し情報を抽出\n */\nfunction extractFromCallExpression(\n callExpr: CallExpression,\n targetId: string,\n paramNames: string[],\n callSiteMap: CallSiteMap,\n): void {\n const sourceFile = callExpr.getSourceFile();\n const filePath = sourceFile.getFilePath();\n\n let info = callSiteMap.get(targetId);\n if (!info) {\n info = new Map();\n callSiteMap.set(targetId, info);\n }\n\n const args = callExpr.getArguments();\n for (let i = 0; i < paramNames.length; i++) {\n const paramName = paramNames[i];\n const arg = args[i];\n const value: ArgValue = arg\n ? extractArgValue(arg)\n : { type: ArgValueType.Undefined };\n\n const argList = info.get(paramName) ?? [];\n argList.push({\n name: paramName,\n value,\n filePath,\n line: callExpr.getStartLineNumber(),\n });\n info.set(paramName, argList);\n }\n}\n\n/**\n * ソースファイルからすべての呼び出し情報を収集する\n */\nexport function collectCallSites(\n sourceFiles: SourceFile[],\n shouldExcludeFile: FileFilter = isTestOrStorybookFile,\n): CallSiteMap {\n const callSiteMap: CallSiteMap = new Map();\n\n for (const sourceFile of sourceFiles) {\n if (shouldExcludeFile(sourceFile.getFilePath())) continue;\n\n // JSX要素を収集\n const jsxElements = [\n ...sourceFile.getDescendantsOfKind(SyntaxKind.JsxOpeningElement),\n ...sourceFile.getDescendantsOfKind(SyntaxKind.JsxSelfClosingElement),\n ];\n\n for (const element of jsxElements) {\n const tagName = element.getTagNameNode();\n if (!Node.isIdentifier(tagName)) continue;\n\n const symbol = tagName.getSymbol();\n // インポートを通じて実際の定義を解決する\n const resolvedSymbol = symbol?.getAliasedSymbol() ?? symbol;\n const decl = resolvedSymbol?.getDeclarations()[0];\n if (!decl) continue;\n\n const declSourceFile = decl.getSourceFile();\n const targetId = createTargetId(\n declSourceFile.getFilePath(),\n tagName.getText(),\n );\n\n extractFromJsxElement(element, targetId, callSiteMap);\n }\n\n // 関数呼び出しを収集\n const callExprs = sourceFile.getDescendantsOfKind(\n SyntaxKind.CallExpression,\n );\n\n for (const callExpr of callExprs) {\n const expr = callExpr.getExpression();\n if (!Node.isIdentifier(expr)) continue;\n\n const symbol = expr.getSymbol();\n const resolvedSymbol = symbol?.getAliasedSymbol() ?? symbol;\n const decl = resolvedSymbol?.getDeclarations()[0];\n if (!decl) continue;\n\n // 関数宣言またはアロー関数を含む変数宣言のみ\n // パラメータを取得\n let paramNames: string[] = [];\n\n if (Node.isFunctionDeclaration(decl)) {\n paramNames = decl.getParameters().map((p) => p.getName());\n } else if (Node.isVariableDeclaration(decl)) {\n const init = decl.getInitializer();\n if (init && Node.isArrowFunction(init)) {\n paramNames = init.getParameters().map((p) => p.getName());\n } else if (init && Node.isFunctionExpression(init)) {\n paramNames = init.getParameters().map((p) => p.getName());\n } else {\n continue;\n }\n } else {\n continue;\n }\n\n const declSourceFile = decl.getSourceFile();\n const targetId = createTargetId(\n declSourceFile.getFilePath(),\n expr.getText(),\n );\n\n extractFromCallExpression(callExpr, targetId, paramNames, callSiteMap);\n }\n }\n\n return callSiteMap;\n}\n","import type { Node } from \"ts-morph\";\nimport {\n type ArgValue,\n ArgValueType,\n type CallSiteMap,\n extractArgValue,\n} from \"./callSiteCollector\";\nimport { argValueToKey, resolveParameterValue } from \"./parameterUtils\";\n\n/**\n * 引数が渡されなかった場合を表す特別な値\n * 必須/任意を問わず、引数未指定の使用箇所を統一的に扱うために使用\n *\n * 注意: この値は文字列 \"[undefined]\" であり、JavaScriptの undefined とは異なる。\n * 解決関数が undefined を返す場合は「値を解決できなかった」ことを意味し、\n * この文字列を返す場合は「実際に undefined が渡された」ことを意味する。\n */\nexport const UNDEFINED_VALUE = \"[undefined]\";\n\n/**\n * 関数型の値を表すプレフィックス\n * コールバック関数など、関数が渡された場合は使用箇所ごとにユニークな値として扱う\n * これにより、同じコールバック関数を渡していても「定数」として検出されない\n */\nexport const FUNCTION_VALUE_PREFIX = \"[function]\";\n\n/**\n * resolveExpressionValueのコンテキスト\n */\nexport interface ResolveContext {\n /** 呼び出し情報(パラメータ参照の解決に使用) */\n callSiteMap: CallSiteMap;\n}\n\n/**\n * 式の実際の値を解決する\n *\n * 異なるファイルで同じenum値やリテラル値を使用している場合でも、\n * 同一の値として認識できるよう、値を正規化して文字列表現で返す。\n * 同名だが異なる定義(別ファイルの同名enum等)を区別するため、\n * 必要に応じてファイルパスを含めた識別子を返す。\n *\n * @param expression - 解決対象の式\n * @param context - 呼び出し情報などのコンテキスト\n */\nexport function resolveExpressionValue(\n expression: Node,\n context: ResolveContext,\n): string {\n const argValue = extractArgValue(expression);\n return argValueToString(argValue, context);\n}\n\n/**\n * ArgValue を文字列表現に変換する\n *\n * @param value - 変換対象の ArgValue\n * @param context - パラメータ参照解決用のコンテキスト\n */\nfunction argValueToString(value: ArgValue, context: ResolveContext): string {\n switch (value.type) {\n case ArgValueType.Literal:\n return value.value;\n\n case ArgValueType.Function:\n // 関数型は使用箇所ごとにユニークな値として扱う\n return `${FUNCTION_VALUE_PREFIX}${value.filePath}:${value.line}`;\n\n case ArgValueType.ParamRef: {\n // パラメータ参照は callSiteMap を使って解決を試みる\n const resolved = resolveParameterValue(value, context.callSiteMap);\n if (resolved !== undefined) {\n return argValueToKey(resolved);\n }\n // 解決できない場合は使用箇所ごとにユニークな値として扱う\n return argValueToKey(value);\n }\n\n case ArgValueType.Undefined:\n return UNDEFINED_VALUE;\n }\n}\n","import { Node } from \"ts-morph\";\nimport {\n type ResolveContext,\n resolveExpressionValue,\n} from \"@/extraction/resolveExpressionValue\";\n\nexport type FlattenedValue = { key: string; value: string };\n\n/**\n * オブジェクトリテラルを再帰的に解析し、フラットなkey-valueペアを返す\n *\n * @param expression - 解析対象の式ノード\n * @param prefix - キー名のプレフィックス(ネストしたプロパティの親パスを表す)\n * @param context - 呼び出し情報などのコンテキスト\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 context: ResolveContext,\n): FlattenedValue[] {\n if (!Node.isObjectLiteralExpression(expression)) {\n // オブジェクトリテラル以外の場合は単一の値として返す\n return [\n { key: prefix, value: resolveExpressionValue(expression, context) },\n ];\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, context)\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(), context),\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 { type ResolveContext, UNDEFINED_VALUE } from \"./resolveExpressionValue\";\n\n/**\n * 使用状況を抽出するユーティリティクラス\n */\nexport class ExtractUsages {\n /**\n * 関数呼び出しから引数の使用状況を抽出する\n *\n * オブジェクトリテラルの場合は再帰的にフラット化し、\n * 各プロパティを「引数名.プロパティ名」形式で記録する。\n *\n * @param callExpression - 関数呼び出しノード\n * @param callable - 対象の関数情報\n * @param context - 呼び出し情報などのコンテキスト\n * @returns 引数使用状況の配列\n */\n static fromCall(\n callExpression: CallExpression,\n callable: Exported,\n context: ResolveContext,\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 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(\n arg,\n param.name,\n context,\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 context - 呼び出し情報などのコンテキスト\n * @returns props使用状況の配列\n */\n static fromJsxElement(\n element: JsxOpeningElement | JsxSelfClosingElement,\n definitions: Definition[],\n context: ResolveContext,\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 context,\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 * 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 {\n type Identifier,\n Node,\n type ParameterDeclaration,\n type ReferencedSymbol,\n} from \"ts-morph\";\nimport type { CallSiteMap } from \"@/extraction/callSiteCollector\";\nimport {\n FUNCTION_VALUE_PREFIX,\n type ResolveContext,\n} from \"@/extraction/resolveExpressionValue\";\nimport { isTestOrStorybookFile } from \"@/source/fileFilters\";\nimport type {\n AnalysisResult,\n AnalyzerOptions,\n ClassifiedDeclaration,\n Constant,\n Definition,\n Exported,\n FileFilter,\n Usage,\n} from \"@/types\";\nimport { getSingleValueFromSet } from \"@/utils/getSingleValueFromSet\";\n\n/**\n * ts-morph の参照情報を表す型\n */\ntype ReferenceEntry = ReturnType<ReferencedSymbol[\"getReferences\"]>[number];\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 protected callSiteMap!: CallSiteMap;\n\n constructor(options: AnalyzerOptions = {}) {\n this.shouldExcludeFile = options.shouldExcludeFile ?? isTestOrStorybookFile;\n this.minUsages = options.minUsages ?? 2;\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 getResolveContext(): ResolveContext {\n return {\n callSiteMap: this.callSiteMap,\n };\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 * @param groupedUsages - 使用状況のグループ(パラメータ名 → 使用状況配列)\n * @param usages - 追加する使用状況の配列\n */\n protected addUsagesToGroup(\n groupedUsages: Record<string, Usage[]>,\n usages: Usage[],\n ): void {\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 /**\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 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 } from \"ts-morph\";\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 * クラスメソッドの引数分析を行う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 declarations - 事前分類済みの宣言配列(type: \"class\")\n * @returns クラスメソッドとその使用状況の配列(名前は「ClassName.methodName」形式)\n */\n protected collect(declarations: ClassifiedDeclaration[]): Exported[] {\n const results: Exported[] = [];\n\n for (const classified of declarations) {\n const { exportName, sourceFile, declaration } = classified;\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 callable: Exported = {\n name: `${exportName}.${methodName}`,\n sourceFilePath: sourceFile.getFilePath(),\n sourceLine: method.getStartLineNumber(),\n definitions: parameters,\n declaration: method,\n usages: {},\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 const groupedUsages: Record<string, Usage[]> = {};\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 callable,\n this.getResolveContext(),\n );\n this.addUsagesToGroup(groupedUsages, usages);\n }\n\n callable.usages = groupedUsages;\n results.push(callable);\n }\n }\n\n return results;\n }\n}\n","import { Node, SyntaxKind } from \"ts-morph\";\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 * 関数の引数分析を行う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 declarations - 事前分類済みの宣言配列(type: \"function\")\n * @returns exportされた関数とその使用状況の配列\n */\n protected collect(declarations: ClassifiedDeclaration[]): Exported[] {\n const results: 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 = this.findFilteredReferences(nameNode);\n\n // 関数の宣言からパラメータ定義を取得\n const parameters = this.getParameterDefinitions(declaration);\n\n const callable: Exported = {\n name: exportName,\n sourceFilePath: sourceFile.getFilePath(),\n sourceLine: declaration.getStartLineNumber(),\n definitions: parameters,\n declaration,\n usages: {},\n };\n\n // 参照から関数呼び出しを抽出し、usagesをパラメータ名ごとにグループ化\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 // 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 callable,\n this.getResolveContext(),\n );\n this.addUsagesToGroup(groupedUsages, usages);\n }\n\n callable.usages = groupedUsages;\n results.push(callable);\n }\n\n return results;\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 { ClassMethodAnalyzer } from \"@/analyzer/classMethodAnalyzer\";\nimport { FunctionAnalyzer } from \"@/analyzer/functionAnalyzer\";\nimport type { CallSiteMap } from \"@/extraction/callSiteCollector\";\nimport { classifyDeclarations } from \"@/source/classifyDeclarations\";\nimport { isTestOrStorybookFile } from \"@/source/fileFilters\";\nimport type { AnalysisResult, FileFilter } from \"@/types\";\n\ninterface AnalyzeFunctionsOptions {\n shouldExcludeFile?: FileFilter;\n minUsages?: number;\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 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 };\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 constants: [...functionResult.constants, ...classMethodResult.constants],\n exported: [...functionResult.exported, ...classMethodResult.exported],\n };\n}\n","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, 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 = this.findFilteredReferences(nameNode);\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 this.getResolveContext(),\n );\n this.addUsagesToGroup(groupedUsages, usages);\n }\n\n component.usages = groupedUsages;\n exportedComponents.push(component);\n }\n\n return exportedComponents;\n }\n}\n","import type { SourceFile } from \"ts-morph\";\nimport { ComponentAnalyzer } from \"@/analyzer/componentAnalyzer\";\nimport type { CallSiteMap } from \"@/extraction/callSiteCollector\";\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 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 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 });\n analyzer.setCallSiteMap(callSiteMap);\n\n return analyzer.analyze(components);\n}\n"],"mappings":"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"}
|