dittory 0.0.5 → 1.0.0
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/README.md +29 -16
- package/dist/cli.mjs +18 -23
- package/dist/cli.mjs.map +1 -1
- package/dist/index.d.mts +3 -4
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
[](https://www.npmjs.com/package/dittory)
|
|
4
4
|
[](https://opensource.org/licenses/MIT)
|
|
5
5
|
|
|
6
|
-
A static analysis CLI that detects **parameters that always receive the same value
|
|
6
|
+
A static analysis CLI for TypeScript projects that detects **parameters that always receive the same value**.
|
|
7
7
|
|
|
8
8
|
## Why?
|
|
9
9
|
|
|
@@ -17,36 +17,49 @@ dittory helps you identify these opportunities automatically.
|
|
|
17
17
|
|
|
18
18
|
## Installation
|
|
19
19
|
|
|
20
|
-
```
|
|
21
|
-
npm
|
|
20
|
+
```sh
|
|
21
|
+
# npm
|
|
22
|
+
$ npm install -D dittory
|
|
23
|
+
|
|
24
|
+
# pnpm
|
|
25
|
+
$ pnpm add -D dittory
|
|
26
|
+
|
|
27
|
+
# yarn
|
|
28
|
+
$ yarn add -D dittory
|
|
29
|
+
|
|
30
|
+
# bun
|
|
31
|
+
$ bun add -D dittory
|
|
22
32
|
```
|
|
23
33
|
|
|
24
|
-
Or
|
|
34
|
+
Or run directly without installing:
|
|
25
35
|
|
|
26
|
-
```
|
|
27
|
-
npx dittory
|
|
36
|
+
```sh
|
|
37
|
+
$ npx dittory
|
|
38
|
+
$ pnpm dlx dittory
|
|
39
|
+
$ yarn dlx dittory
|
|
40
|
+
$ bunx dittory
|
|
28
41
|
```
|
|
29
42
|
|
|
30
43
|
## Usage
|
|
31
44
|
|
|
32
|
-
```
|
|
45
|
+
```sh
|
|
33
46
|
# Analyze ./src directory (default)
|
|
34
|
-
dittory
|
|
47
|
+
$ dittory
|
|
35
48
|
|
|
36
49
|
# Analyze a specific directory
|
|
37
|
-
dittory ./path/to/src
|
|
50
|
+
$ dittory ./path/to/src
|
|
38
51
|
|
|
39
52
|
# Set minimum usage count (default: 2)
|
|
40
|
-
dittory --min=3
|
|
53
|
+
$ dittory --min=3
|
|
41
54
|
|
|
42
55
|
# Analyze specific targets
|
|
43
|
-
dittory --target=components # React components only
|
|
44
|
-
dittory --target=functions # Functions and class methods only
|
|
45
|
-
dittory --target=all # Both (default)
|
|
56
|
+
$ dittory --target=components # React components only
|
|
57
|
+
$ dittory --target=functions # Functions and class methods only
|
|
58
|
+
$ dittory --target=all # Both (default)
|
|
46
59
|
|
|
47
60
|
# Output mode
|
|
48
|
-
dittory --output=simple # Show only constant parameters (default)
|
|
49
|
-
dittory --output=verbose # Also show all analyzed functions
|
|
61
|
+
$ dittory --output=simple # Show only constant parameters (default)
|
|
62
|
+
$ dittory --output=verbose # Also show all analyzed functions
|
|
50
63
|
```
|
|
51
64
|
|
|
52
65
|
## Example Output
|
|
@@ -274,7 +287,7 @@ const data = await fetchData(id);
|
|
|
274
287
|
## Requirements
|
|
275
288
|
|
|
276
289
|
- Node.js >= 18
|
|
277
|
-
-
|
|
290
|
+
- TypeScript project with a `tsconfig.json`
|
|
278
291
|
|
|
279
292
|
## License
|
|
280
293
|
|
package/dist/cli.mjs
CHANGED
|
@@ -64,10 +64,9 @@ async function loadJsConfig(configPath) {
|
|
|
64
64
|
}
|
|
65
65
|
const VALID_TARGETS$1 = [
|
|
66
66
|
"all",
|
|
67
|
-
"components",
|
|
67
|
+
"react-components",
|
|
68
68
|
"functions"
|
|
69
69
|
];
|
|
70
|
-
const VALID_OUTPUTS$1 = ["simple", "verbose"];
|
|
71
70
|
/**
|
|
72
71
|
* コンフィグの値を検証する
|
|
73
72
|
*/
|
|
@@ -81,9 +80,9 @@ function validateConfig(config) {
|
|
|
81
80
|
if (!VALID_TARGETS$1.includes(config.target)) throw new Error(`Invalid config: target must be one of ${VALID_TARGETS$1.join(", ")}, got ${config.target}`);
|
|
82
81
|
result.target = config.target;
|
|
83
82
|
}
|
|
84
|
-
if ("
|
|
85
|
-
if (
|
|
86
|
-
result.
|
|
83
|
+
if ("debug" in config) {
|
|
84
|
+
if (typeof config.debug !== "boolean") throw new Error(`Invalid config: debug must be a boolean, got ${typeof config.debug}`);
|
|
85
|
+
result.debug = config.debug;
|
|
87
86
|
}
|
|
88
87
|
if ("tsconfig" in config) {
|
|
89
88
|
if (typeof config.tsconfig !== "string" || config.tsconfig === "") throw new Error(`Invalid config: tsconfig must be a non-empty string, got ${config.tsconfig}`);
|
|
@@ -111,7 +110,7 @@ const DEFAULT_OPTIONS = {
|
|
|
111
110
|
targetDir: "./src",
|
|
112
111
|
minUsages: 2,
|
|
113
112
|
target: "all",
|
|
114
|
-
|
|
113
|
+
debug: false,
|
|
115
114
|
tsconfig: "./tsconfig.json",
|
|
116
115
|
allowedValueTypes: "all"
|
|
117
116
|
};
|
|
@@ -123,15 +122,14 @@ var CliValidationError = class extends Error {
|
|
|
123
122
|
};
|
|
124
123
|
const VALID_TARGETS = [
|
|
125
124
|
"all",
|
|
126
|
-
"components",
|
|
125
|
+
"react-components",
|
|
127
126
|
"functions"
|
|
128
127
|
];
|
|
129
|
-
const VALID_OUTPUTS = ["simple", "verbose"];
|
|
130
128
|
/** 不明なオプションの検出に使用 */
|
|
131
129
|
const VALID_OPTIONS = [
|
|
132
130
|
"--min",
|
|
133
131
|
"--target",
|
|
134
|
-
"--
|
|
132
|
+
"--debug",
|
|
135
133
|
"--tsconfig",
|
|
136
134
|
"--value-types",
|
|
137
135
|
"--help"
|
|
@@ -160,11 +158,8 @@ function parseCliOptions(args) {
|
|
|
160
158
|
const value = arg.slice(9);
|
|
161
159
|
if (!VALID_TARGETS.includes(value)) throw new CliValidationError(`Invalid value for --target: "${value}" (valid values: ${VALID_TARGETS.join(", ")})`);
|
|
162
160
|
result.target = value;
|
|
163
|
-
} else if (arg
|
|
164
|
-
|
|
165
|
-
if (!VALID_OUTPUTS.includes(value)) throw new CliValidationError(`Invalid value for --output: "${value}" (valid values: ${VALID_OUTPUTS.join(", ")})`);
|
|
166
|
-
result.output = value;
|
|
167
|
-
} else if (arg.startsWith("--tsconfig=")) {
|
|
161
|
+
} else if (arg === "--debug") result.debug = true;
|
|
162
|
+
else if (arg.startsWith("--tsconfig=")) {
|
|
168
163
|
const value = arg.slice(11);
|
|
169
164
|
if (value === "") throw new CliValidationError("Invalid value for --tsconfig: path cannot be empty");
|
|
170
165
|
result.tsconfig = value;
|
|
@@ -208,8 +203,8 @@ Usage: dittory [options] [directory]
|
|
|
208
203
|
|
|
209
204
|
Options:
|
|
210
205
|
--min=<number> Minimum usage count (default: 2)
|
|
211
|
-
--target=<mode> Analysis target: all, components, functions (default: all)
|
|
212
|
-
--
|
|
206
|
+
--target=<mode> Analysis target: all, react-components, functions (default: all)
|
|
207
|
+
--debug Show verbose output (default: false)
|
|
213
208
|
--tsconfig=<path> Path to tsconfig.json (default: ./tsconfig.json)
|
|
214
209
|
--value-types=<types> Value types to detect: boolean, number, string, enum, undefined, all (default: all)
|
|
215
210
|
Multiple types can be specified with comma: --value-types=boolean,string
|
|
@@ -273,8 +268,8 @@ function printStatistics(result) {
|
|
|
273
268
|
/**
|
|
274
269
|
* 解析結果を出力
|
|
275
270
|
*/
|
|
276
|
-
function printAnalysisResult(result,
|
|
277
|
-
if (
|
|
271
|
+
function printAnalysisResult(result, debug) {
|
|
272
|
+
if (debug) printDeclarations(result.declarations);
|
|
278
273
|
if (result.constantParams.isEmpty()) console.log("No arguments with constant values were found.");
|
|
279
274
|
else printConstantParams(result.constantParams);
|
|
280
275
|
printStatistics(result);
|
|
@@ -323,11 +318,11 @@ async function main() {
|
|
|
323
318
|
if (error instanceof Error) exitWithError(error.message);
|
|
324
319
|
throw error;
|
|
325
320
|
}
|
|
326
|
-
const { targetDir, minUsages, target,
|
|
321
|
+
const { targetDir, minUsages, target, debug, tsconfig, allowedValueTypes } = {
|
|
327
322
|
targetDir: path.resolve(cliOptions.targetDir ?? fileConfig.targetDir ?? DEFAULT_OPTIONS.targetDir),
|
|
328
323
|
minUsages: cliOptions.minUsages ?? fileConfig.minUsages ?? DEFAULT_OPTIONS.minUsages,
|
|
329
324
|
target: cliOptions.target ?? fileConfig.target ?? DEFAULT_OPTIONS.target,
|
|
330
|
-
|
|
325
|
+
debug: cliOptions.debug ?? fileConfig.debug ?? DEFAULT_OPTIONS.debug,
|
|
331
326
|
tsconfig: cliOptions.tsconfig ?? fileConfig.tsconfig ?? DEFAULT_OPTIONS.tsconfig,
|
|
332
327
|
allowedValueTypes: cliOptions.allowedValueTypes ?? fileConfig.allowedValueTypes ?? DEFAULT_OPTIONS.allowedValueTypes
|
|
333
328
|
};
|
|
@@ -343,7 +338,7 @@ async function main() {
|
|
|
343
338
|
if (error instanceof CliValidationError) exitWithError(error.message);
|
|
344
339
|
throw error;
|
|
345
340
|
}
|
|
346
|
-
if (
|
|
341
|
+
if (debug) {
|
|
347
342
|
console.log(`Target directory: ${targetDir}`);
|
|
348
343
|
console.log(`Minimum usage count: ${minUsages}`);
|
|
349
344
|
console.log(`Analysis target: ${target}\n`);
|
|
@@ -352,7 +347,7 @@ async function main() {
|
|
|
352
347
|
const callSiteMap = new CallSiteCollector().collect(sourceFilesToAnalyze);
|
|
353
348
|
const declarationsToMerge = [];
|
|
354
349
|
const constantParamsToMerge = [];
|
|
355
|
-
if (target === "all" || target === "components") {
|
|
350
|
+
if (target === "all" || target === "react-components") {
|
|
356
351
|
const propsResult = analyzePropsCore(sourceFilesToAnalyze, {
|
|
357
352
|
minUsages,
|
|
358
353
|
allowedValueTypes,
|
|
@@ -373,7 +368,7 @@ async function main() {
|
|
|
373
368
|
printAnalysisResult({
|
|
374
369
|
declarations: AnalyzedDeclarations.merge(...declarationsToMerge),
|
|
375
370
|
constantParams: ConstantParams.merge(...constantParamsToMerge)
|
|
376
|
-
},
|
|
371
|
+
}, debug);
|
|
377
372
|
}
|
|
378
373
|
main().catch((error) => {
|
|
379
374
|
console.error(error);
|
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>>","declarationsToMerge: AnalyzedDeclarations[]","constantParamsToMerge: ConstantParams[]"],"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 {\n VALID_VALUE_TYPES,\n type ValueType,\n} from \"@/extraction/valueTypeDetector\";\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 allowedValueTypes?: 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.allowedValueTypes = \"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.allowedValueTypes = 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 {\n VALID_VALUE_TYPES,\n type ValueType,\n} from \"@/extraction/valueTypeDetector\";\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 allowedValueTypes?: 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 allowedValueTypes: 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 allowedValueTypes: \"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.allowedValueTypes = 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 } from \"@/domain/analysisResult\";\nimport type { AnalyzedDeclarations } from \"@/domain/analyzedDeclarations\";\nimport type { ConstantParams } from \"@/domain/constantParams\";\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 */\nfunction printDeclarations(declarations: AnalyzedDeclarations): void {\n const lines = [\n \"Collecting exported functions...\",\n ` → Found ${declarations.length} function(s)`,\n ...declarations.map(\n (decl) =>\n ` - ${bold(green(decl.name))} (${path.relative(\n process.cwd(),\n decl.sourceFilePath,\n )})`,\n ),\n \"\",\n ];\n console.log(lines.join(\"\\n\"));\n}\n\n/**\n * 常に同じ値が渡されている引数を出力\n */\nfunction printConstantParams(constantParams: ConstantParams): void {\n if (constantParams.isEmpty()) {\n return;\n }\n\n const grouped = constantParams.groupByDeclaration();\n\n for (const constantParamGroup of grouped) {\n const relativePath = path.relative(\n process.cwd(),\n constantParamGroup.declarationSourceFile,\n );\n const usageCount = constantParamGroup.params[0]?.usageCount ?? 0;\n // 使用箇所は全パラメータで同じなので、最初のパラメータから取得\n const usages = constantParamGroup.params[0]?.usages ?? [];\n\n console.log(\n `${bold(green(constantParamGroup.declarationName))} ${relativePath}:${constantParamGroup.declarationLine}`,\n );\n console.log(\"Constant Arguments:\");\n\n for (const param of constantParamGroup.params) {\n console.log(` - ${param.paramName} = ${param.value.outputString()}`);\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.declarations.length;\n const functionsWithConstants =\n result.constantParams.groupByDeclaration().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 printDeclarations(result.declarations);\n }\n\n if (result.constantParams.isEmpty()) {\n console.log(\"No arguments with constant values were found.\");\n } else {\n printConstantParams(result.constantParams);\n }\n\n printStatistics(result);\n}\n","import path from \"node:path\";\nimport { Project, type SourceFile } from \"ts-morph\";\nimport type { FileFilter } from \"@/domain/analyzerOptions\";\nimport { isTestOrStorybookFile } from \"@/source/fileFilters\";\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 type { AnalysisResult } from \"@/domain/analysisResult\";\nimport { AnalyzedDeclarations } from \"@/domain/analyzedDeclarations\";\nimport { ConstantParams } from \"@/domain/constantParams\";\nimport { CallSiteCollector } from \"@/extraction/callSiteCollector\";\nimport { printAnalysisResult } from \"@/output/printAnalysisResult\";\nimport { createFilteredSourceFiles } from \"@/source/createFilteredSourceFiles\";\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 allowedValueTypes:\n cliOptions.allowedValueTypes ??\n fileConfig.allowedValueTypes ??\n DEFAULT_OPTIONS.allowedValueTypes,\n };\n\n const { targetDir, minUsages, target, output, tsconfig, allowedValueTypes } =\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 = new CallSiteCollector().collect(sourceFilesToAnalyze);\n\n // 各解析結果を収集\n const declarationsToMerge: AnalyzedDeclarations[] = [];\n const constantParamsToMerge: ConstantParams[] = [];\n\n if (target === \"all\" || target === \"components\") {\n const propsResult = analyzePropsCore(sourceFilesToAnalyze, {\n minUsages,\n allowedValueTypes,\n callSiteMap,\n });\n declarationsToMerge.push(propsResult.declarations);\n constantParamsToMerge.push(propsResult.constantParams);\n }\n\n if (target === \"all\" || target === \"functions\") {\n const functionsResult = analyzeFunctionsCore(sourceFilesToAnalyze, {\n minUsages,\n allowedValueTypes,\n callSiteMap,\n });\n declarationsToMerge.push(functionsResult.declarations);\n constantParamsToMerge.push(functionsResult.constantParams);\n }\n\n const result: AnalysisResult = {\n declarations: AnalyzedDeclarations.merge(...declarationsToMerge),\n constantParams: ConstantParams.merge(...constantParamsToMerge),\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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"}
|
|
1
|
+
{"version":3,"file":"cli.mjs","names":["config: unknown","VALID_TARGETS: readonly AnalyzeMode[]","result: DittoryConfig","VALID_TARGETS","DEFAULT_OPTIONS: ResolvedOptions","VALID_TARGETS: readonly AnalyzeMode[]","VALID_OPTIONS: readonly string[]","result: RawCliOptions","cliOptions: ReturnType<typeof parseCliOptions>","fileConfig: Awaited<ReturnType<typeof loadConfig>>","declarationsToMerge: AnalyzedDeclarations[]","constantParamsToMerge: ConstantParams[]"],"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 {\n VALID_VALUE_TYPES,\n type ValueType,\n} from \"@/extraction/valueTypeDetector\";\nimport type { AnalyzeMode } 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 debug?: boolean;\n tsconfig?: string;\n targetDir?: string;\n allowedValueTypes?: 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 \"react-components\",\n \"functions\",\n];\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 (\"debug\" in config) {\n if (typeof config.debug !== \"boolean\") {\n throw new Error(\n `Invalid config: debug must be a boolean, got ${typeof config.debug}`,\n );\n }\n result.debug = config.debug;\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.allowedValueTypes = \"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.allowedValueTypes = 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 {\n VALID_VALUE_TYPES,\n type ValueType,\n} from \"@/extraction/valueTypeDetector\";\n\nexport type AnalyzeMode = \"all\" | \"react-components\" | \"functions\";\n\n/**\n * CLI で明示的に指定されたオプション(デフォルト値なし)\n */\nexport interface RawCliOptions {\n targetDir?: string;\n minUsages?: number;\n target?: AnalyzeMode;\n debug?: boolean;\n tsconfig?: string;\n allowedValueTypes?: ValueType[];\n showHelp: boolean;\n}\n\n/**\n * 解決済みのオプション(デフォルト値適用後)\n */\nexport interface ResolvedOptions {\n targetDir: string;\n minUsages: number;\n target: AnalyzeMode;\n debug: boolean;\n tsconfig: string;\n allowedValueTypes: ValueType[] | \"all\";\n}\n\n/** デフォルトのオプション値 */\nexport const DEFAULT_OPTIONS: ResolvedOptions = {\n targetDir: \"./src\",\n minUsages: 2,\n target: \"all\",\n debug: false,\n tsconfig: \"./tsconfig.json\",\n allowedValueTypes: \"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 \"react-components\",\n \"functions\",\n];\n\n/** 不明なオプションの検出に使用 */\nconst VALID_OPTIONS: readonly string[] = [\n \"--min\",\n \"--target\",\n \"--debug\",\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 === \"--debug\") {\n result.debug = true;\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.allowedValueTypes = 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, react-components, functions (default: all)\n --debug Show verbose output (default: false)\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 { AnalysisResult } from \"@/domain/analysisResult\";\nimport type { AnalyzedDeclarations } from \"@/domain/analyzedDeclarations\";\nimport type { ConstantParams } from \"@/domain/constantParams\";\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 */\nfunction printDeclarations(declarations: AnalyzedDeclarations): void {\n const lines = [\n \"Collecting exported functions...\",\n ` → Found ${declarations.length} function(s)`,\n ...declarations.map(\n (decl) =>\n ` - ${bold(green(decl.name))} (${path.relative(\n process.cwd(),\n decl.sourceFilePath,\n )})`,\n ),\n \"\",\n ];\n console.log(lines.join(\"\\n\"));\n}\n\n/**\n * 常に同じ値が渡されている引数を出力\n */\nfunction printConstantParams(constantParams: ConstantParams): void {\n if (constantParams.isEmpty()) {\n return;\n }\n\n const grouped = constantParams.groupByDeclaration();\n\n for (const constantParamGroup of grouped) {\n const relativePath = path.relative(\n process.cwd(),\n constantParamGroup.declarationSourceFile,\n );\n const usageCount = constantParamGroup.params[0]?.usageCount ?? 0;\n // 使用箇所は全パラメータで同じなので、最初のパラメータから取得\n const usages = constantParamGroup.params[0]?.usages ?? [];\n\n console.log(\n `${bold(green(constantParamGroup.declarationName))} ${relativePath}:${constantParamGroup.declarationLine}`,\n );\n console.log(\"Constant Arguments:\");\n\n for (const param of constantParamGroup.params) {\n console.log(` - ${param.paramName} = ${param.value.outputString()}`);\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.declarations.length;\n const functionsWithConstants =\n result.constantParams.groupByDeclaration().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 debug: boolean,\n): void {\n if (debug) {\n printDeclarations(result.declarations);\n }\n\n if (result.constantParams.isEmpty()) {\n console.log(\"No arguments with constant values were found.\");\n } else {\n printConstantParams(result.constantParams);\n }\n\n printStatistics(result);\n}\n","import path from \"node:path\";\nimport { Project, type SourceFile } from \"ts-morph\";\nimport type { FileFilter } from \"@/domain/analyzerOptions\";\nimport { isTestOrStorybookFile } from \"@/source/fileFilters\";\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 type { AnalysisResult } from \"@/domain/analysisResult\";\nimport { AnalyzedDeclarations } from \"@/domain/analyzedDeclarations\";\nimport { ConstantParams } from \"@/domain/constantParams\";\nimport { CallSiteCollector } from \"@/extraction/callSiteCollector\";\nimport { printAnalysisResult } from \"@/output/printAnalysisResult\";\nimport { createFilteredSourceFiles } from \"@/source/createFilteredSourceFiles\";\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 debug: cliOptions.debug ?? fileConfig.debug ?? DEFAULT_OPTIONS.debug,\n tsconfig:\n cliOptions.tsconfig ?? fileConfig.tsconfig ?? DEFAULT_OPTIONS.tsconfig,\n allowedValueTypes:\n cliOptions.allowedValueTypes ??\n fileConfig.allowedValueTypes ??\n DEFAULT_OPTIONS.allowedValueTypes,\n };\n\n const { targetDir, minUsages, target, debug, tsconfig, allowedValueTypes } =\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 (debug) {\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 = new CallSiteCollector().collect(sourceFilesToAnalyze);\n\n // 各解析結果を収集\n const declarationsToMerge: AnalyzedDeclarations[] = [];\n const constantParamsToMerge: ConstantParams[] = [];\n\n if (target === \"all\" || target === \"react-components\") {\n const propsResult = analyzePropsCore(sourceFilesToAnalyze, {\n minUsages,\n allowedValueTypes,\n callSiteMap,\n });\n declarationsToMerge.push(propsResult.declarations);\n constantParamsToMerge.push(propsResult.constantParams);\n }\n\n if (target === \"all\" || target === \"functions\") {\n const functionsResult = analyzeFunctionsCore(sourceFilesToAnalyze, {\n minUsages,\n allowedValueTypes,\n callSiteMap,\n });\n declarationsToMerge.push(functionsResult.declarations);\n constantParamsToMerge.push(functionsResult.constantParams);\n }\n\n const result: AnalysisResult = {\n declarations: AnalyzedDeclarations.merge(...declarationsToMerge),\n constantParams: ConstantParams.merge(...constantParamsToMerge),\n };\n\n printAnalysisResult(result, debug);\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
|
@@ -317,8 +317,7 @@ interface AnalyzePropsOptions {
|
|
|
317
317
|
declare function analyzePropsCore(sourceFiles: SourceFile[], options: AnalyzePropsOptions): AnalysisResult;
|
|
318
318
|
//#endregion
|
|
319
319
|
//#region src/cli/parseCliOptions.d.ts
|
|
320
|
-
type AnalyzeMode = "all" | "components" | "functions";
|
|
321
|
-
type OutputMode = "simple" | "verbose";
|
|
320
|
+
type AnalyzeMode = "all" | "react-components" | "functions";
|
|
322
321
|
//#endregion
|
|
323
322
|
//#region src/cli/loadConfig.d.ts
|
|
324
323
|
/**
|
|
@@ -327,7 +326,7 @@ type OutputMode = "simple" | "verbose";
|
|
|
327
326
|
interface DittoryConfig {
|
|
328
327
|
minUsages?: number;
|
|
329
328
|
target?: AnalyzeMode;
|
|
330
|
-
|
|
329
|
+
debug?: boolean;
|
|
331
330
|
tsconfig?: string;
|
|
332
331
|
targetDir?: string;
|
|
333
332
|
allowedValueTypes?: ValueType[] | "all";
|
|
@@ -377,5 +376,5 @@ declare class CallSiteCollector {
|
|
|
377
376
|
private getDeclaration;
|
|
378
377
|
}
|
|
379
378
|
//#endregion
|
|
380
|
-
export { type AnalysisResult, type AnalyzeMode, type AnalyzedDeclaration, CallSiteCollector, CallSiteMap, type ConstantParam, type Definition, type DittoryConfig, type FileFilter, type
|
|
379
|
+
export { type AnalysisResult, type AnalyzeMode, type AnalyzedDeclaration, CallSiteCollector, CallSiteMap, type ConstantParam, type Definition, type DittoryConfig, type FileFilter, type Usage, analyzeFunctionsCore, analyzePropsCore };
|
|
381
380
|
//# sourceMappingURL=index.d.mts.map
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dittory",
|
|
3
|
-
"version": "0.0
|
|
4
|
-
"description": "A static analysis CLI that detects parameters that always receive the same value
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "A static analysis CLI for TypeScript projects that detects parameters that always receive the same value",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.mjs",
|
|
7
7
|
"types": "./dist/index.d.mts",
|