code-migration

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

code-migration

代码迁移

jscpd --compare SOURCE TARGET
pairs every function of one folder with the function of the other folder that does the same job, in any pair of languages, and lists the functions that have no counterpart. This skill uses it to measure a port: what is ported, what is left, and whether the port you just wrote was recognized.
A port runs in two phases: the tests first, then the code they check. A coverage report of the source's tests tells which tests exercise which function, so each function is ported together with the tests that prove it works. See Plan: tests first, then code.
Two words are used throughout:
  • source: the implementation you port from, such as the iOS app, the Python library, or the JavaScript package;
  • target: the implementation you port to, such as the Android app, the Rust crate, or the TypeScript rewrite.
Neither has to be old or new. The source may stay in production and keep changing, and the target may already hold features the source lacks. For two implementations that both live on (
ios/
and
android/
), the same report shows parity; see Parity.
jscpd finds functions in JavaScript, TypeScript, JSX, TSX, Vue, Svelte, Astro, Python, Rust, Go, Java, Kotlin, C#, C, C++, PHP, Ruby, Scala and Swift. The compare-codebases skill explains how the comparison pairs functions and how to check its result; the jscpd skill covers the rest of the tool.
jscpd --compare SOURCE TARGET
可以跨任意两种语言,将一个文件夹中的每个函数与另一个文件夹中功能相同的函数配对,并列出没有对应实现的函数。本技能使用该命令来衡量移植进度:已移植内容、剩余工作量,以及你刚写的移植代码是否能被识别。
移植工作分为两个阶段:先移植测试,再移植测试所校验的代码。源项目测试的覆盖率报告可以说明哪些测试覆盖了哪些函数,因此每个函数都要和验证其功能的测试一起移植。参见计划:测试先行,代码随后。
全文会使用以下两个术语:
  • source(源):你要移植的原始实现,例如iOS应用、Python库或JavaScript包;
  • target(目标):移植后的目标实现,例如Android应用、Rust crate或TypeScript重写版本。
两者没有新旧之分。源项目可能仍在生产环境中运行并持续迭代,目标项目也可能已经具备源项目没有的功能。对于两个同时存续的实现(如
ios/
和
android/
),同一份报告可以展示两者的一致性;参见一致性校验。
jscpd支持识别JavaScript、TypeScript、JSX、TSX、Vue、Svelte、Astro、Python、Rust、Go、Java、Kotlin、C#、C、C++、PHP、Ruby、Scala和Swift语言中的函数。compare-codebases 技能说明了比对时如何配对函数以及如何校验结果;jscpd 技能则涵盖了该工具的其他功能。

Setup

安装配置

--compare
pairs functions with a code embedding model that runs inside jscpd. Download it once (548 MB, CodeRankEmbed). Ask the user before you start the download:
bash
npx jscpd --semantic-download
jscpd caches the vectors, so a repeat run embeds only the functions whose code changed and takes seconds.
Pick the two paths and keep them fixed for the whole port: the source first, the target second. Two paths are required, and they must not overlap (
app/
and
app/android/
is refused). The target may be empty at the start.
One run measures both phases: the report has a
Code
block and a
Tests
block (a
code
and a
tests
section in JSON), and a test pairs only with a test. jscpd tells a test by the conventions of its language: test files such as
*_test.go
,
test_*.py
,
*.test.ts
or
*Test.java
, folders such as
tests/
,
__tests__/
or
src/test/
, Rust tests in
#[cfg(test)]
modules, and JavaScript test cases such as
it('rounds cents', () => …)
. Phase 1 reads the
Tests
block, phase 2 the
Code
block.
--compare
功能通过运行在jscpd内部的代码嵌入模型来配对函数。你需要下载一次该模型(548 MB,CodeRankEmbed)。开始下载前请先询问用户:
bash
npx jscpd --semantic-download
jscpd会缓存向量,因此重复运行时只会对代码发生变更的函数重新生成嵌入,耗时仅需数秒。
选择两个路径并在整个移植过程中保持固定:第一个是源路径,第二个是目标路径。必须提供两个路径,且路径不能重叠(例如
app/
和
app/android/
会被拒绝)。目标路径在初始时可以为空。
一次运行即可衡量两个阶段的进度:报告包含
Code
(代码)块和
Tests
(测试)块(JSON格式中为
code
和
tests
字段),且测试只会与测试配对。jscpd会根据对应语言的约定识别测试:例如
*_test.go
、
test_*.py
、
*.test.ts
或
*Test.java
等测试文件,
tests/
、
__tests__
或
src/test/
等测试文件夹,
#[cfg(test)]
模块中的Rust测试,以及
it('rounds cents', () => …)
形式的JavaScript测试用例。第一阶段读取
Tests
块,第二阶段读取
Code
块。

Measure

进度衡量

Run the console report for yourself and for the user. Here a Python billing library is being ported to TypeScript:
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 first line is the port's progress: the share of the source's functions that have a counterpart in the target. "Only in" the source is the work left.
  • The second line and "Only in" the target describe the target's own code: helpers the port needed, features the source never had, or a port jscpd did not recognize (see Misses).
  • Each file row gives its paired functions, the mean similarity of their pairs (with the number of
    low
    pairs, if any), and the counterpart file: the file on the other side that holds most of its counterparts. Use the counterpart to decide where a missing function goes.
  • "Paired under other names" lists the pairs whose names differ even once case and underscores are ignored: renamed ports, constructors, platform names. Each has its similarity and level (see Reading pairs). These are ported already, even though a search by name would not find them.
  • With an empty target the report is one line of totals plus
    billing-ts/ has no functions yet
    .
For work you plan and track, read the JSON report instead of the console:
bash
npx jscpd --compare billing-py/ billing-ts/ -r json -o .jscpd-compare --silent
.jscpd-compare/jscpd-compare.json
has
sides[0]
(the source) and
sides[1]
(the target), each with
path
,
functions
,
matched
,
percentage
,
files
(
file
,
functions
,
matched
,
counterpart
,
similarity
,
lowPairs
) and
unmatched
(
file
,
name
,
start
,
end
), and
pairs
, each with
a
(source),
b
(target),
similarity
,
level
(
high
,
medium
,
low
),
renamed
(the names differ) and
matchedBy
(
code
or
name
). Paths are relative to each side's folder. Add
.jscpd-compare/
to
.gitignore
or write the report outside the repository.
-r console-full
also prints every pair with its similarity, and
-r markdown
writes
jscpd-compare.md
, a table you can paste into a PR description or a tracking issue.
运行控制台报告供你和用户查看。以下是将Python计费库移植到TypeScript的示例:
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
  • 第一行是移植进度:源项目中已在目标项目找到对应实现的函数占比。源项目“独有”的函数就是剩余的工作量。
  • 第二行以及目标项目“独有”的内容描述了目标项目的自有代码:移植过程中需要的辅助函数、源项目从未有过的功能,或是jscpd未识别到的移植代码(参见遗漏情况)。
  • 每个文件行显示其配对的函数数量、配对的平均相似度(如有低相似度配对会标注数量),以及对应文件:另一侧包含最多对应函数的文件。可通过对应文件确定缺失函数应该放在哪里。
  • “异名配对”列出了即使忽略大小写和下划线后名称仍不相同的配对:包括重命名的移植函数、构造函数、平台相关命名等。每个配对都有对应的相似度和等级(参见解读配对结果)。这些函数已经完成移植,只是通过名称搜索无法找到。
  • 如果目标路径为空,报告只会显示一行总计,外加
    billing-ts/ has no functions yet
    (billing-ts/ 暂无函数)的提示。
对于需要规划和跟踪的工作,建议读取JSON报告而非控制台输出:
bash
npx jscpd --compare billing-py/ billing-ts/ -r json -o .jscpd-compare --silent
.jscpd-compare/jscpd-compare.json
包含
sides[0]
(源项目)和
sides[1]
(目标项目),每一侧都有
path
(路径)、
functions
(函数总数)、
matched
(已配对数)、
percentage
(百分比)、
files
(文件列表,包含
file
文件名、
functions
函数数、
matched
已配对数、
counterpart
对应文件、
similarity
相似度、
lowPairs
低相似度配对数)和
unmatched
(未配对列表,包含
file
文件名、
name
函数名、
start
起始行、
end
结束行),以及
pairs
(配对列表,每个配对包含
a
源端函数、
b
目标端函数、
similarity
相似度、
level
等级(
high
高/
medium
中/
low
低)、
renamed
是否重命名、
matchedBy
匹配方式(
code
代码/
name
名称))。路径均为相对于各侧文件夹的相对路径。请将
.jscpd-compare/
加入
.gitignore
,或将报告生成到仓库之外。
-r console-full
会打印每个配对的相似度,
-r markdown
会生成
jscpd-compare.md
文件,里面的表格可以直接粘贴到PR描述或跟踪issue中。

Plan: tests first, then code

计划:测试先行,代码随后

Port the tests before the code. Ported tests define, before any target code exists, what it means for each function to be ported: when the function lands, its tests pass or show what still differs. A port written before its tests can only be checked against your reading of the source.
先移植测试,再移植代码。在目标代码尚未存在时,已移植的测试可以定义每个函数的移植标准:当函数实现完成后,其对应的测试要么通过,要么能显示出仍存在的差异。如果先写移植代码再写测试,就只能靠你对源项目代码的理解来校验正确性。

Bind tests to functions with coverage

通过覆盖率将测试与函数绑定

A test's name does not say which functions it runs: a test of
checkout
also runs
apply_discount
and
tax_for_region
. Coverage does. Before porting anything, build a map from each source function to the tests that exercise it.
  1. Get a coverage report of the source's tests that says which test ran which lines: recorded per test, or per test file when the project's tooling has no per-test mode.
  2. Take the source's functions and their lines from the
    code
    section of the JSON report. Every counted function is either in the source's
    unmatched
    list or the
    a
    side of a pair, each with
    file
    ,
    name
    ,
    start
    and
    end
    . Functions under
    --min-tokens
    or
    --min-lines
    are not listed there; take those from the coverage report's own function list, which most formats have.
  3. A test covers a function when it runs a line from the function's
    start
    to its
    end
    . Write the map both ways, function to tests and test to functions, to a file next to the report (for example
    .jscpd-compare/test-map.json
    ), and rebuild it when the source's tests change.
Use the map for three things:
  • which tests to port with a function, and which functions a ported test needs before it can pass;
  • the source functions no test covers. Before porting such a function, write a test for it on the source side that records what it does today (a characterization test), and port that test with the others. A port with no test has nothing to check it;
  • the order of work: tests that need few functions come first, since they turn green soonest.
Coverage says which code a test runs, not what it checks. A function that a test only passes through on the way to another is weakly bound to that test; prefer the tests whose names or assertions are about the function.
测试的名称并不能说明它运行了哪些函数:一个
checkout
(结账)的测试也会运行
apply_discount
(应用折扣)和
tax_for_region
(区域计税)函数。覆盖率可以说明这一点。在开始移植前,先构建一个映射表,记录源项目中每个函数对应的测试用例。
  1. 获取源项目测试的覆盖率报告,明确每个测试运行了哪些代码行:优先按单测试粒度记录,如果项目工具不支持单测试模式,则按测试文件粒度记录。
  2. 从JSON报告的
    code
    部分提取源项目的函数及其行号范围。所有被统计的函数要么在源项目的
    unmatched
    (未配对)列表中,要么在配对的
    a
    (源)端,每个都带有
    file
    (文件)、
    name
    (名称)、
    start
    (起始行)和
    end
    (结束行)信息。低于
    --min-tokens
    或
    --min-lines
    阈值的函数不会出现在列表中;这类函数可以从覆盖率报告自带的函数列表中获取,大多数覆盖率格式都支持该列表。
  3. 如果一个测试运行了某个函数从
    start
    到
    end
    之间的任意一行,就认为该测试覆盖了这个函数。请将双向映射(函数到测试、测试到函数)写入报告旁边的文件中(例如
    .jscpd-compare/test-map.json
    ),并在源项目测试发生变更时重新生成。
该映射表有三个用途:
  • 确定移植某个函数时需要同时移植哪些测试,以及一个已移植的测试需要哪些函数实现后才能通过;
  • 找出没有测试覆盖的源函数。在移植这类函数之前,先在源项目侧为其编写测试,记录其当前的行为(即特性测试),然后将该测试与其他测试一起移植。没有测试的移植代码无法验证正确性;
  • 安排工作顺序:依赖函数少的测试优先移植,因为它们最快能通过。
覆盖率只能说明测试运行了哪些代码,不能说明测试校验了什么。如果一个函数只是测试在调用其他函数的路径上经过的,那么它与该测试的绑定关系很弱;应优先选择名称或断言与该函数直接相关的测试。

Phase 1: port the tests

第一阶段:移植测试

  1. Take the source's unmatched tests from the
    tests
    section of the JSON report. A JavaScript or TypeScript test case written as a callback,
    it('rounds cents', () => …)
    , goes by its title, so it pairs with
    test_rounds_cents
    in pytest or
    rounds_cents
    in Rust like any named test.
  2. Port the tests file by file, into the target's test layout, against the API the target will have. Keep inputs and expected values exactly as they are. Where the target must behave differently (a platform limit, a language's number types), write the difference into the test with a comment and tell the user.
  3. A ported test calls functions that do not exist yet, so it fails. In a compiled language it stops the whole test build. Keep such tests out of the build until their functions land, for example a module declaration left commented out, a cfg feature or an excluded source set, and turn them on one by one in phase 2. Do not write empty functions to make the tests compile: a stub under a source name can pair by name and count as ported.
  4. Run the target's tests. The ported tests that pass already cover what the target has; the failing or disabled ones, read against the test map, are the work list for phase 2.
  1. 从JSON报告的
    tests
    部分提取源项目中未配对的测试。以回调形式编写的JavaScript或TypeScript测试用例(如
    it('rounds cents', () => …)
    )会以其标题作为标识,因此可以像其他命名测试一样,与pytest中的
    test_rounds_cents
    或Rust中的
    rounds_cents
    配对。
  2. 逐文件移植测试,按照目标项目的测试目录结构存放,并基于目标项目将要提供的API编写。保持测试的输入和预期值完全不变。如果目标项目必须有不同的行为(例如平台限制、语言的数字类型差异),请在测试中用注释说明差异,并告知用户。
  3. 已移植的测试会调用尚未存在的函数,因此会失败。在编译型语言中,这甚至会导致整个测试构建失败。在对应函数实现完成之前,请将这类测试排除在构建之外,例如注释掉模块声明、使用cfg特性或排除源码集,然后在第二阶段逐一启用。不要为了让测试通过编译而编写空函数:一个与源函数同名的存根函数可能会通过名称配对,被误计为已移植。
  4. 运行目标项目的测试。已通过的移植测试覆盖了目标项目已有的功能;失败或被禁用的测试,结合测试映射表来看,就是第二阶段的工作清单。

Phase 2: port the code

第二阶段:移植代码

Port the code one function at a time, with its tests already in place.
  1. Run the JSON report and take the source's
    unmatched
    list from its
    code
    section.
  2. Order the work. Start with functions whose tests are ported, and port a function after the functions it calls, since a caller ported before its helpers has nothing to call. Within that order, finish one file before starting the next, so each target file fills up in one go.
  3. Before writing a port, make sure the target does not have it already. Pairs with
    renamed: true
    are ported functions under other names; they are not in
    unmatched
    , but check them when the user asks where a function went. Then search the target for an equivalent that jscpd missed: in the counterpart file from
    files
    , under other names (constructors, merged functions, a platform or library call that replaces the helper). If one exists, do not port the function again. Note the equivalent and move on.
  4. Write the port in the counterpart file, or in the file the target's layout puts it in. Keep the name recognizable in the target language's convention (
    encodeBinary
    becomes
    encode_binary
    in Rust or Python), because jscpd pairs short functions by name. Follow the style of pairs that are already done in the same file.
    console-full
    lists them.
  5. Turn on the function's ported tests, as the test map lists them, and run them. When they fail, fix the port, not the test, unless the user agrees that the target should behave differently. A pair in the report says the two functions look alike; the tests say whether they behave alike.
  6. Run the target's coverage for those tests and check that they run the new function. A ported test that never reaches it is bound to something else on the target (a helper, a mock, an old path), and it proves nothing about the port.
  7. Run the comparison again with the same paths and options. Check that the function has left
    unmatched
    , that its pair joins the right function in the target, and its level. A
    high
    pair is done. A
    low
    pair to your port usually means the port differs a lot from the source in structure; read both and make sure the behavior matches. A pair to a different function means the port is not recognized, or it resembles the wrong thing; read both before going on.
  8. Report progress to the user with both measures, as the reports print them: tests ported and passing, and functions ported (
    functions 73% → 76%, 3 ported: …; tests 41 of 44 ported, 38 passing
    ), with the functions you decided not to port and why.
Repeat until the source's
unmatched
list holds only functions you decided not to port. Typical reasons: dead code (check with
npx jscpd --dead-code
on the source where it supports the language), code the target platform or its libraries provide (a JSON parser, a retry helper), platform glue with no equivalent on the target (an iOS delegate callback, an Android notification channel), and features the user decided not to carry over. List these in a notes file or the PR, one line each, so the remaining percentage is explained.
When the source keeps changing during the port, a function that was paired can come back as unmatched after a rewrite, and new source functions join the list. Rerun the report before each batch of work instead of reusing an old list.
逐函数移植代码,且对应的测试已提前就位。
  1. 运行JSON报告,从
    code
    部分提取源项目的
    unmatched
    (未配对)列表。
  2. 安排工作顺序。从测试已完成移植的函数开始,并且要在函数依赖的其他函数移植完成后再移植该函数——因为如果先移植调用方,它将没有可调用的依赖函数。在这个顺序下,先完成一个文件的所有移植工作再开始下一个文件,这样每个目标文件可以一次性补全。
  3. 在编写移植代码之前,先确认目标项目中是否已经存在该函数。
    renamed: true
    的配对是使用了其他名称的已移植函数;它们不在
    unmatched
    列表中,但当用户询问某个函数的去向时可以查看这些配对。然后在目标项目中搜索jscpd遗漏的等效实现:可以在
    files
    列表给出的对应文件中,查找其他名称的实现(构造函数、合并后的函数、替代该辅助函数的平台或库调用)。如果已经存在等效实现,就不要再重复移植该函数,记录下来后继续下一项工作。
  4. 将移植代码写在对应文件中,或者目标项目目录结构指定的文件中。函数名称要按照目标语言的命名规范保持可识别性(例如
    encodeBinary
    在Rust或Python中变为
    encode_binary
    ),因为jscpd会通过名称配对短函数。请遵循同一文件中已完成配对的代码风格。可以通过
    console-full
    模式查看这些已配对的函数。
  5. 按照测试映射表的记录,启用该函数对应的已移植测试并运行。如果测试失败,请修复移植代码而非测试,除非用户同意目标项目应该有不同的行为。报告中的配对只能说明两个函数看起来相似;测试才能验证它们的行为是否一致。
  6. 运行这些测试在目标项目中的覆盖率,检查它们是否确实执行了新移植的函数。如果一个已移植的测试从未调用到该函数,说明它绑定到了目标项目中的其他实现(辅助函数、mock、旧路径),无法验证移植代码的正确性。
  7. 使用相同的路径和参数重新运行比对。检查该函数是否已从
    unmatched
    列表中移除,它是否与目标项目中正确的函数配对,以及配对的等级。
    high
    (高)等级的配对表示完成。如果你的移植代码配对等级为
    low
    (低),通常意味着移植代码在结构上与源函数差异很大;请阅读两边的代码,确保行为一致。如果配对到了错误的函数,说明移植代码没有被正确识别,或者它和错误的函数更相似;继续工作前请先检查两边的代码。
  8. 按照报告中的输出,用两个指标向用户汇报进度:已移植并通过的测试数,以及已移植的函数数(例如“函数 73% → 76%,已移植3个:…;测试 44个中已移植41个,38个通过”),同时说明你决定不移植的函数及其原因。
重复上述步骤,直到源项目的
unmatched
列表中只剩下你决定不移植的函数。常见的不移植原因包括:死代码(可在支持的语言上使用
npx jscpd --dead-code
检查源项目)、目标平台或其库已提供的代码(如JSON解析器、重试辅助函数)、目标平台没有等效实现的平台粘合代码(如iOS的代理回调、Android的通知渠道),以及用户决定不保留的功能。请将这些内容记录在备注文件或PR中,每行一条,这样剩余的百分比就有了合理的解释。
如果移植过程中源项目仍在持续变更,那么已经配对的函数可能会在重写后重新变为未配对状态,同时源项目还会新增函数。请在每批工作开始前重新运行报告,而不是复用旧的列表。

Parity between two implementations

两个实现之间的一致性校验

When both implementations live on, as the iOS and the Android app do,
npx jscpd --compare ios/ android/
shows parity. Read both "Only in" lists:
  • a function on one side only is either platform code (Android notification channels, iOS delegate callbacks) or a feature the other side lacks. Report each missing feature to the user before implementing it;
  • to add a missing feature, treat the side that has it as the source for that feature: bind its tests with coverage, port the tests, then the code, as in Plan: tests first, then code.
jscpd pairs functions within modules that already match. A module is the folder right under the deepest folder each side's files share, so keep the two trees parallel (
ios/<plugin>
and
android/<plugin>
) for the best pairing.
当两个实现同时存续时(例如iOS和Android应用),
npx jscpd --compare ios/ android/
可以展示两者的一致性。请查看两边的“独有”列表:
  • 仅在一侧存在的函数,要么是平台相关代码(Android通知渠道、iOS代理回调),要么是另一侧缺少的功能。在实现缺失功能之前,请先向用户汇报每个缺失的功能;
  • 要添加缺失的功能,请将拥有该功能的一侧作为该功能的源端:通过覆盖率绑定其测试,先移植测试,再移植代码,流程参见计划:测试先行,代码随后。
jscpd会在已经匹配的模块内配对函数。模块是指两侧文件共享的最深层目录下的直接子目录,因此请保持两个目录树结构平行(例如
ios/<plugin>
和
android/<plugin>
),以获得最佳的配对效果。

Reading pairs

解读配对结果

  • level
    rates the similarity on the scale of the model in use, so it means the same with every model.
    high
    (0.7125 and up with the default CodeRankEmbed, across languages) is almost always the same function.
    medium
    is usually the same function, restructured.
    low
    needs reading both functions, since related code pairs there too (a function counting UTF-8 bytes paired with one converting a string to them). On the Tauri plugins, every
    low
    pair joined two differently named functions, and one of them joined two different plugins.
  • matchedBy: code
    means the two functions are each other's closest match and the similarity stands out.
  • matchedBy: name
    means the names match once case and underscores are ignored, and the code is similar enough. It catches short ports. Read these pairs, because a same-named function can do something else.
  • The similarity does not see small differences in behavior. Two versions that drifted apart still pair. Only tests and reading the code catch drift.
  • level
    (等级)基于所使用模型的评分体系来衡量相似度,因此在任何模型下含义都是一致的。
    high
    (高,默认CodeRankEmbed模型跨语言阈值为0.7125及以上)几乎总是同一个函数。
    medium
    (中)通常是经过重构的同一个函数。
    low
    (低)则需要阅读两边的代码来确认,因为相关的代码也可能配对(例如统计UTF-8字节数的函数和将字符串转换为UTF-8字节的函数)。在Tauri插件的比对中,所有
    low
    等级的配对都连接了两个不同名称的函数,其中有一个配对甚至连接了两个不同的插件。
  • matchedBy: code
    (匹配方式:代码)表示两个函数是彼此最接近的匹配,且相似度明显高于其他候选。
  • matchedBy: name
    (匹配方式:名称)表示忽略大小写和下划线后两个函数的名称匹配,且代码相似度足够。这种方式可以捕获短函数的移植。请仔细检查这些配对,因为同名函数也可能实现不同的功能。
  • 相似度无法识别行为上的细微差异。两个已经产生分歧的版本仍可能配对。只有测试和代码审查才能发现行为偏差。

Misses

遗漏情况

jscpd compares functions only: types, constants, enums with data, SQL and UI markup (storyboards, Android layouts) are not in the report, so port and check them yourself. Known gaps in pairing:
  • constructors across languages (a Java constructor and Rust's
    new
    , Kotlin's
    constructor
    , Swift's
    init
    ) pair only when their code is similar enough;
  • a short function renamed in the port (
    add_history
    for
    _finder_penalty_add_history
    );
  • one function split into several, or several merged into one: the report may pair only the closest part and list the rest as unmatched;
  • anonymous functions (callbacks, closures) take no part at all, except JavaScript and TypeScript test cases such as
    it('rounds cents', () => …)
    , which go by their titles.
A function listed only in the target that you know is a port of a source function is one of these. Do not rename working code only to raise the number, and never add stubs or empty functions with source names: jscpd may pair a stub by name, and the progress would then report work that was not done.
jscpd仅比对函数:类型、常量、带数据的枚举、SQL和UI标记(storyboards、Android布局)不会出现在报告中,因此需要你自行移植和检查。已知的配对盲区包括:
  • 跨语言的构造函数(Java的构造函数与Rust的
    new
    、Kotlin的
    constructor
    、Swift的
    init
    )只有在代码足够相似时才会配对;
  • 移植时被重命名的短函数(例如
    add_history
    对应
    _finder_penalty_add_history
    );
  • 一个函数拆分为多个,或多个函数合并为一个:报告可能只会配对最相似的部分,其余部分则列为未配对;
  • 匿名函数(回调、闭包)完全不参与比对,不过
    it('rounds cents', () => …)
    这类JavaScript和TypeScript测试用例除外,它们会以标题作为标识参与配对。
如果某个只出现在目标项目中的函数,你确定它是某个源函数的移植版本,那大概率属于上述情况之一。不要为了提高进度数字而重命名正常工作的代码,也绝对不要添加与源函数同名的存根或空函数:jscpd可能会通过名称配对存根,导致进度报告虚报未完成的工作。

Options

参数选项

  • --min-tokens
    (30 with
    --compare
    ) and
    --min-lines
    (5) decide which functions count toward the totals. Smaller functions still pair as partners. Raise them to focus on substantial functions, and keep them fixed across runs so percentages compare.
  • --ignore
    leaves files out, such as generated code or fixtures;
    --pattern
    narrows the comparison to some files, such as the tests of one module. Keep the globs fixed across runs.
  • --format
    narrows the walk to some languages, for example when the source mixes the code being ported with build scripts.
  • --semantic-model
    and
    --semantic-url
    pick another embedding model or an OpenAI-compatible API (
    npx jscpd --semantic-models
    lists the models with calibrated thresholds). Keep the model fixed across runs, because each model scores on its own scale.
  • The exit code is 0 whatever the progress, so the command does not fail a build on its own.
  • --min-tokens
    (使用
    --compare
    时默认30)和
    --min-lines
    (默认5)决定了哪些函数会被计入总数。更小的函数仍会参与配对。可以调高这些阈值来关注核心函数,并且在多次运行中保持参数不变,这样百分比才具有可比性。
  • --ignore
    用于排除文件,例如生成的代码或测试夹具;
    --pattern
    可以将比对范围缩小到某些文件,例如某个模块的测试。请在多次运行中保持这些glob模式不变。
  • --format
    可以将遍历范围限定为指定语言,例如当源项目中混合了待移植代码和构建脚本时使用。
  • --semantic-model
    和
    --semantic-url
    用于选择其他嵌入模型或兼容OpenAI的API(运行
    npx jscpd --semantic-models
    可列出带有校准阈值的模型列表)。请在多次运行中保持模型不变,因为每个模型的评分体系都不同。
  • 无论进度如何,命令的退出码都是0,因此该命令本身不会导致构建失败。

Rules

规则

  • Keep the two paths, their order, the options and the model the same from run to run, or the numbers stop being comparable.
  • Port the tests before the code, and a function only together with the tests the coverage map binds to it.
  • Treat the report as a map of what to read and what is left. It does not prove the port is correct; tests do.
  • Never game the percentage: no stubs, no renames made only for jscpd, no deleting source functions to shrink the denominator without the user's decision.
  • When the report and your reading of the code disagree, trust the code and tell the user which pair looked wrong.
  • 每次运行都要保持两个路径、路径顺序、参数和模型不变,否则进度数字将不具备可比性。
  • 先移植测试再移植代码,并且每个函数必须和覆盖率映射表中绑定的测试一起移植。
  • 将报告视为需要查阅的内容和剩余工作量的指引图。它不能证明移植是正确的;测试才能证明。
  • 绝对不要在百分比上作弊:不要使用存根函数,不要只为了jscpd识别而重命名代码,未经用户同意不要删除源函数来缩小分母。
  • 当报告与你对代码的理解不一致时,请以代码为准,并告知用户哪个配对看起来有问题。