compare-codebases
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
Chinesecompare-codebases
代码库比较
jscpd --compare A BABjscpd --compare A BABHow the comparison works
比较功能的工作原理
jscpd finds the functions of JavaScript, TypeScript, JSX, TSX, Vue, Svelte, Astro, Python, Rust, Go, Java, Kotlin, C#, C, C++, PHP, Ruby, Scala and Swift files in both folders. It turns the code of each function into a vector with a code embedding model that runs inside jscpd (CodeRankEmbed by default), so two functions that do the same job point the same way even when their languages, names and structure differ. Functions of one folder are never compared with each other.
Functions pair in two steps:
- By code. Two functions pair when each is the other's closest match in the other folder, their cosine similarity reaches the model's threshold (0.4125 across languages and 0.6375 within one language, with CodeRankEmbed), and the similarity stands out from the function's other matches. A function close to the best one also pairs when it reaches a higher bar, so a feature written twice on one side gets two pairs. Functions shorter than (30 with
--min-tokens) or--compare(5) stay out of this step, because a short function resembles too many others.--min-lines - By name. A function left over pairs with a function of the other folder under the same name, once case and underscores are ignored (,
encodeBinary,encode_binary), when their similarity reaches the_encode_binarylevel (0.5625 across languages with CodeRankEmbed). A name pair skips the closest-match and stand-out checks of the first step, so it needs more than that step's threshold; otherwise everymediumandloadof two codebases would pair. Size does not matter here, so a short port is found. A name pair has to stay within modules the first step linked. A module is the folder right under the deepest folder all files of a side share (initinnotification); a file that sits higher than the rest, such as a build script, does not move that folder up. Two modules link when one holds the most of the other's code pairs. Soandroid/notification/…of one plugin does not pair with its namesake in another.checkPermissions
Each pair gets a level on the scale of the model, because a cosine that is high for one model is low for another:
| Level | With CodeRankEmbed, across languages | Meaning |
|---|---|---|
| 0.7125 and up | almost always the same function |
| 0.5625 to 0.7125 | usually the same function, restructured |
| 0.4125 to 0.5625 | read both: related code pairs here too |
Totals count the functions of at least tokens and lines; smaller ones appear only as partners. Anonymous functions (callbacks, closures) take no part. Types, constants, SQL and UI markup are not compared.
--min-tokens--min-linesjscpd 会在两个文件夹中识别 JavaScript、TypeScript、JSX、TSX、Vue、Svelte、Astro、Python、Rust、Go、Java、Kotlin、C#、C、C++、PHP、Ruby、Scala 和 Swift 文件中的函数。它通过 jscpd 内置的代码嵌入模型(默认为 CodeRankEmbed)将每个函数的代码转换为向量,因此即使两个函数的语言、名称和结构不同,只要实现相同功能,它们的向量方向就会一致。同一文件夹内的函数之间不会进行比较。
函数配对分为两个步骤:
-
按代码匹配。 当两个函数互为对方文件夹中的最匹配项,且它们的余弦相似度达到模型阈值(使用 CodeRankEmbed 时,跨语言阈值为 0.4125,同语言阈值为 0.6375),同时该相似度在该函数的所有匹配结果中显著突出时,二者会配对。如果与最佳匹配项接近的另一个函数达到了更高的阈值,也会完成配对,因此一侧中重复实现的某个功能会对应两个配对。长度短于(使用
--min-tokens时默认为 30)或--compare(默认为 5)的函数不会参与此步骤,因为短函数与其他函数相似的概率过高。--min-lines -
按名称匹配。 未在第一步中配对的函数,若忽略大小写和下划线后(如、
encodeBinary、encode_binary)与另一文件夹中的函数名称相同,且二者相似度达到_encode_binary(中等)级别(使用 CodeRankEmbed 跨语言时为 0.5625),则会完成配对。按名称配对会跳过第一步中的“最匹配项”和“显著性”检查,因此它的相似度要求高于第一步的阈值;否则两个代码库中所有名为medium和load的函数都会被配对。此步骤中函数长度不影响匹配,因此可以识别出短函数的移植版本。名称配对必须发生在第一步中已建立关联的模块之间。模块指的是某一侧所有文件共享的最深层目录的下一级文件夹(例如init中的android/notification/…);位置比其他文件更高的文件(如构建脚本)不会抬高该共享目录的层级。当一个模块包含了另一个模块的大部分代码配对时,两个模块就会建立关联。因此,一个插件中的notification函数不会与另一个插件中的同名函数配对。checkPermissions
每个配对都会根据模型的评分等级获得一个级别,因为不同模型的余弦相似度分值标准不同:
| 级别 | 使用 CodeRankEmbed 跨语言时的分值 | 含义 |
|---|---|---|
| 0.7125 及以上 | 几乎可以确定是同一个函数 |
| 0.5625 至 0.7125 | 通常是同一个函数,经过了结构调整 |
| 0.4125 至 0.5625 | 建议同时阅读两端代码:相关代码也可能出现在此级别中 |
统计总数时仅计入长度至少满足 和 要求的函数;更小的函数仅会作为配对的另一方出现。匿名函数(回调、闭包)不参与比较。类型、常量、SQL 和 UI 标记语言不在比较范围内。
--min-tokens--min-linesA way to compare two folders
两个文件夹的比较流程
1. Pick the folders
1. 选择待比较的文件夹
- Point at the code, not the repositories: and
app/src/main/java, not the two repository roots. Build output, vendored code and generated files dilute the result; jscpd already skips whatios/Sourcesexcludes, and.gitignoretakes more globs.--ignore - Two folders are required, and they must not overlap: and
app/is refused.app/android/ - Keep parallel structures when you can (and
ios/<module>). Modules steer the name step, so matching folder names help.android/<module> - For a port, put the source first and the target second, so the first line of the report is the port's progress. For two implementations that both live on, the order does not matter.
- Decide about tests. Production code and tests answer different questions: compare them separately (with
src/,src/withtests/), or leave tests out withtests/.--ignore "**/__tests__/**,**/*.test.*,**/test/**"
- 指向代码目录而非仓库根目录:应选择 和
app/src/main/java这类目录,而非两个仓库的根目录。构建产物、第三方依赖代码和生成的文件会稀释比较结果;jscpd 会自动跳过ios/Sources排除的内容,还可以通过.gitignore参数添加更多 glob 模式来忽略文件。--ignore - 必须提供两个文件夹,且二者不能有重叠:例如 和
app/会被拒绝。app/android/ - 尽量保持文件夹结构平行(如 和
ios/<模块名>)。模块会影响名称匹配步骤,因此对应的文件夹名称有助于提升匹配准确度。android/<模块名> - 如果是代码移植场景,将源文件夹放在前面,目标文件夹放在后面,这样报告的第一行就会显示移植进度。如果是两个并行维护的实现,顺序无关紧要。
- 决定是否包含测试代码。生产代码和测试代码对应的问题不同:可以分开比较(与
src/比,src/与tests/比),或者通过tests/参数排除测试文件。--ignore "**/__tests__/**,**/*.test.*,**/test/**"
2. Get the model
2. 获取模型
The first run needs the model (548 MB). Ask the user before downloading it:
bash
npx jscpd --semantic-downloadjscpd caches the vectors per pair of folders, so later runs embed only the functions whose code changed.
首次运行需要下载模型(大小为 548 MB)。下载前请先询问用户:
bash
npx jscpd --semantic-downloadjscpd 会按文件夹对缓存向量,因此后续运行时只会对代码发生变更的函数重新生成嵌入。
3. Read the overview
3. 查看概览报告
bash
npx jscpd --compare billing-py/ billing-ts/text
71% 5 of 7 functions in billing-py/ have a counterpart in billing-ts/
80% 4 of 5 functions in billing-ts/ have a counterpart in billing-py/
billing-py/
file paired similarity counterpart
billing.py 4 / 5 0.89 billing.ts
shipping.py 1 / 2 0.91 shipping.ts
billing-ts/
file paired similarity counterpart
billing.ts 3 / 4 0.89 billing.py
shipping.ts 1 / 1 0.91 shipping.py
Paired under other names (1):
billing-py/ billing-ts/ similarity
billing.py:28 tax_for_region billing.ts:27 salesTax 0.87 high
Only in billing-py/ (2):
billing.py (1)
46 due_date 6 lines
shipping.py (1)
18 estimate_delivery_days 8 lines
Only in billing-ts/ (1):
billing.ts (1)
39 toCurrency 8 lines- The two top lines give the share of each folder's functions that have a counterpart in the other.
- The file tables give each file's paired functions, the mean similarity of its pairs (with the number of pairs, as in
low), and the file on the other side that holds most of its counterparts.0.62, 1 low - "Paired under other names" lists the pairs whose names differ even once case and underscores are ignored. A search by name never finds these.
- "Only in" lists the functions with no counterpart, per folder, grouped by file with the number of each file's functions, then each function's first line, name and length.
- In colour, levels are green (), yellow (
high) and red (medium), and the shares are green when complete, yellow when partial and red when nothing is paired. Passlowwhen you parse the console output, or read the JSON report instead.--no-colors - If one folder has no functions, the report is one line of totals and . Check the path and the languages before concluding anything else.
<folder> has no functions yet
bash
npx jscpd --compare billing-py/ billing-ts/text
71% 5 of 7 functions in billing-py/ have a counterpart in billing-ts/
80% 4 of 5 functions in billing-ts/ have a counterpart in billing-py/
billing-py/
file paired similarity counterpart
billing.py 4 / 5 0.89 billing.ts
shipping.py 1 / 2 0.91 shipping.ts
billing-ts/
file paired similarity counterpart
billing.ts 3 / 4 0.89 billing.py
shipping.ts 1 / 1 0.91 shipping.py
Paired under other names (1):
billing-py/ billing-ts/ similarity
billing.py:28 tax_for_region billing.ts:27 salesTax 0.87 high
Only in billing-py/ (2):
billing.py (1)
46 due_date 6 lines
shipping.py (1)
18 estimate_delivery_days 8 lines
Only in billing-ts/ (1):
billing.ts (1)
39 toCurrency 8 lines- 最上方的两行显示每个文件夹中在另一文件夹里有对应项的函数占比。
- 文件表格展示每个文件的已配对函数数量、配对的平均相似度(同时标注 级别的配对数,例如
low),以及另一侧包含其最多对应项的文件。0.62, 1 low - “异名配对(Paired under other names)”部分列出了即使忽略大小写和下划线后名称仍不相同的配对。这类配对无法通过名称搜索找到。
- “仅存在于(Only in)”部分按文件夹列出没有对应项的函数,按文件分组并标注每个文件的函数数量,随后是每个函数的起始行号、名称和长度。
- 在彩色输出中,级别分别用绿色()、黄色(
high)和红色(medium)表示;占比的颜色则是:全部配对为绿色,部分配对为黄色,无配对为红色。如果你需要解析控制台输出,请添加low参数,或者直接读取 JSON 报告。--no-colors - 如果其中一个文件夹没有函数,报告只会显示一行统计信息和 (该文件夹暂无函数)。在得出其他结论前,请先检查路径和支持的语言是否正确。
<folder> has no functions yet
4. Check the pairs
4. 检查配对结果
bash
npx jscpd --compare billing-py/ billing-ts/ -r console-fullconsole-full- two or three pairs, to confirm the pairing works on this code;
high - every pair, since related code pairs there too (a client call and the endpoint it calls, a function counting UTF-8 bytes and one converting a string to them);
low - the pairs marked , since a same-named function can do something else.
by name
When a sample pair is wrong, say which one and why.
bash
npx jscpd --compare billing-py/ billing-ts/ -r console-fullconsole-full- 抽取两到三个 级别的配对,确认配对逻辑适用于当前代码;
high - 检查所有 级别的配对,因为相关代码也可能出现在这个级别中(例如客户端调用和它调用的接口、统计 UTF-8 字节数的函数和将字符串转换为 UTF-8 字节的函数);
low - 检查标记为 (按名称匹配)的配对,因为同名函数可能实现的是不同的功能。
by name
如果抽样发现某个配对是错误的,请说明具体是哪一个以及错误原因。
5. Check the functions with no counterpart
5. 检查无对应项的函数
A function in an "Only in" list is either missing on the other side, or its counterpart was not recognized. Before calling it missing, search the other folder: in its counterpart file, under other names, as part of a larger function, or replaced by a library or platform call. The known gaps in pairing:
- constructors across languages (a Java constructor and Rust's , Kotlin's
new, Swift'sconstructor) pair only when their code is similar enough;init - a short function renamed on the other side (for
add_history);_finder_penalty_add_history - one function split into several, or several merged into one: only the closest part may pair.
Functions that exist on one side only by design are expected: platform glue (an iOS delegate callback, an Android notification channel), helpers a language needs and another does not, features one side dropped.
“Only in”列表中的函数,要么是另一侧确实缺失该函数,要么是其对应项未被识别。在判定函数缺失之前,请在另一个文件夹中搜索确认:可能在对应的文件中、可能使用了其他名称、可能被整合到了一个更大的函数中,也可能被库或平台调用替代了。目前已知的配对盲区包括:
- 跨语言的构造函数(例如 Java 的构造函数与 Rust 的 、Kotlin 的
new、Swift 的constructor)只有在代码足够相似时才会配对;init - 短函数在另一侧被重命名(例如 对应
add_history);_finder_penalty_add_history - 一个函数被拆分为多个,或多个函数被合并为一个:只有最相似的部分可能会配对。
有些函数仅存在于一侧是设计使然,属于正常情况:例如平台适配代码(iOS 的代理回调、Android 的通知渠道)、某一种语言需要而另一种语言不需要的辅助函数、某一侧已下线的功能。
6. Report
6. 输出报告
For the user, summarize in a few lines: the two percentages, the notable renamed pairs, the pairs you checked and what you found, and the real gaps on each side, grouped by file or module. For a document or an issue, write the Markdown report and attach or paste it:
lowbash
npx jscpd --compare billing-py/ billing-ts/ -r markdown -o .jscpd-compareFor your own processing, read the JSON report (, written to ). It has and , each with , , , , (, , , , , ) and (, , , ), and , each with , , , , and . Paths are relative to each folder. Write reports outside the repository or add the folder to .
-r jsonjscpd-compare.jsonsides[0]sides[1]pathfunctionsmatchedpercentagefilesfilefunctionsmatchedcounterpartsimilaritylowPairsunmatchedfilenamestartendpairsabsimilaritylevelrenamedmatchedBy.gitignore向用户反馈时,用几句话总结:两个文件夹的配对占比、值得注意的异名配对、你检查过的 级别配对及发现的问题,以及每一侧真正缺失的函数(按文件或模块分组)。如果需要写入文档或 Issue,可以生成 Markdown 报告并附件或粘贴:
lowbash
npx jscpd --compare billing-py/ billing-ts/ -r markdown -o .jscpd-compare如果需要自行处理数据,可以读取 JSON 报告(通过 参数生成,写入 文件)。报告包含 和 两个数组,每个元素包含 (路径)、(函数总数)、(已配对数)、(占比)、(文件列表,每个文件包含 、、、、、 字段)和 (未配对函数列表,每个函数包含 、、、 字段);此外还有 数组,每个配对包含 、、、、 和 字段。所有路径均相对于对应文件夹。请将报告生成在仓库外部,或者将报告文件夹加入 。
-r jsonjscpd-compare.jsonsides[0]sides[1]pathfunctionsmatchedpercentagefilesfilefunctionsmatchedcounterpartsimilaritylowPairsunmatchedfilenamestartendpairsabsimilaritylevelrenamedmatchedBy.gitignoreOptions that change the result
影响结果的可选参数
- and
--min-tokensset which functions count. Raise them to focus on substantial functions.--min-lines - and
--ignorenarrow the files, for example to leave out tests or build scripts.--format - picks another model, and
--semantic-modelan OpenAI-compatible embeddings API;--semantic-urllists the models with calibrated thresholds.npx jscpd --semantic-modelsand--semantic-thresholdmove the thresholds; change them only after reading pairs on both sides of the new value.--semantic-same-threshold - embeds everything again, which is needed only when the cache is suspect.
--semantic-rebuild-cache
Keep the folders, their order, the options and the model the same when you compare runs over time, or the numbers stop being comparable. The exit code is 0 whatever the result.
- 和
--min-tokens用于设定参与统计的函数门槛。调高这两个参数可以聚焦于更核心的函数。--min-lines - 和
--ignore用于缩小文件范围,例如排除测试文件或构建脚本。--format - 用于选择其他模型,
--semantic-model用于指定兼容 OpenAI 格式的嵌入 API;--semantic-url命令可以列出所有已校准阈值的模型。npx jscpd --semantic-models和--semantic-threshold用于调整阈值;只有在查看过新阈值上下的配对结果后再修改这两个参数。--semantic-same-threshold - 会强制重新生成所有嵌入的向量,仅在怀疑缓存有问题时使用。
--semantic-rebuild-cache
如果需要多次运行比较以追踪进度,请保持文件夹、文件夹顺序、参数和模型都一致,否则统计数据将不具备可比性。无论比较结果如何,命令的退出码始终为 0。
Limits
局限性
- Similarity does not see small differences in behavior: two versions that drifted apart still pair, often at . Only tests and reading the code catch drift.
high - The more one side is restructured, the fewer of its functions pair by code.
- Only functions are compared.
- The model runs on the CPU. The first run embedded about 25 functions a second on an Apple M1 (261 functions in 11 seconds) and can be several times slower on a small CI machine; later runs reuse the cache.
- 相似度无法识别行为上的细微差异:两个已经产生分歧的版本仍可能配对,而且通常是 级别。只有通过测试和代码审查才能发现功能偏差。
high - 一侧的代码重构程度越高,通过代码匹配到的函数数量就越少。
- 仅对函数进行比较。
- 模型在 CPU 上运行。在 Apple M1 芯片上,首次运行每秒可处理约 25 个函数(261 个函数耗时 11 秒);在配置较低的 CI 机器上速度可能慢数倍;后续运行会复用缓存,速度更快。