[{"data":1,"prerenderedAt":1685},["ShallowReactive",2],{"navigation_docs":3,"blog-post-jscpd-5-4-2":249,"blog-views-jscpd-5-4-2":1683},[4,29,104,124,194],{"title":5,"icon":6,"path":7,"stem":8,"children":9},"Start",false,"\u002Fstart","1.start",[10,14,19,24],{"title":11,"path":7,"stem":12,"icon":13},"What jscpd finds","1.start\u002Findex","i-lucide-house",{"title":15,"path":16,"stem":17,"icon":18},"Installation","\u002Fstart\u002Finstallation","1.start\u002F2.installation","i-lucide-download",{"title":20,"path":21,"stem":22,"icon":23},"Quickstart","\u002Fstart\u002Fquickstart","1.start\u002F3.quickstart","i-lucide-rocket",{"title":25,"path":26,"stem":27,"icon":28},"Configuration basics","\u002Fstart\u002Fconfiguration","1.start\u002F4.configuration","i-lucide-settings-2",{"title":30,"icon":6,"path":31,"stem":32,"children":33,"page":6},"Guides","\u002Fguides","2.guides",[34,39,44,49,54,59,64,69,74,79,84,89,94,99],{"title":35,"path":36,"stem":37,"icon":38},"Use jscpd in CI","\u002Fguides\u002Fci","2.guides\u002F01.ci","i-lucide-github",{"title":40,"path":41,"stem":42,"icon":43},"Pre-commit hooks","\u002Fguides\u002Fpre-commit","2.guides\u002F02.pre-commit","i-lucide-git-commit",{"title":45,"path":46,"stem":47,"icon":48},"Fail only on new duplication","\u002Fguides\u002Fbaseline","2.guides\u002F03.baseline","i-lucide-git-compare",{"title":50,"path":51,"stem":52,"icon":53},"Monorepos","\u002Fguides\u002Fmonorepos","2.guides\u002F04.monorepos","i-lucide-folder-tree",{"title":55,"path":56,"stem":57,"icon":58},"Cross-format detection","\u002Fguides\u002Fcross-format","2.guides\u002F05.cross-format","i-lucide-layers",{"title":60,"path":61,"stem":62,"icon":63},"Agents","\u002Fguides\u002Fagents","2.guides\u002F06.agents","i-lucide-cpu",{"title":65,"path":66,"stem":67,"icon":68},"Editors","\u002Fguides\u002Feditors","2.guides\u002F07.editors","i-lucide-code",{"title":70,"path":71,"stem":72,"icon":73},"Duplication over git history","\u002Fguides\u002Fhistory","2.guides\u002F08.history","i-lucide-chart-no-axes-combined",{"title":75,"path":76,"stem":77,"icon":78},"Dashboard and health score","\u002Fguides\u002Fdashboard","2.guides\u002F09.dashboard","i-lucide-layout-dashboard",{"title":80,"path":81,"stem":82,"icon":83},"Dead code","\u002Fguides\u002Fdead-code","2.guides\u002F10.dead-code","i-lucide-ghost",{"title":85,"path":86,"stem":87,"icon":88},"Comparing two codebases","\u002Fguides\u002Fcompare","2.guides\u002F11.compare","i-lucide-git-compare-arrows",{"title":90,"path":91,"stem":92,"icon":93},"Semantic clones","\u002Fguides\u002Fsemantic-clones","2.guides\u002F12.semantic-clones","i-lucide-brain-circuit",{"title":95,"path":96,"stem":97,"icon":98},"Similar functions","\u002Fguides\u002Fsimilarity","2.guides\u002F13.similarity","i-lucide-list-tree",{"title":100,"path":101,"stem":102,"icon":103},"Boundaries","\u002Fguides\u002Fboundaries","2.guides\u002F14.boundaries","i-lucide-fence",{"title":105,"icon":6,"path":106,"stem":107,"children":108,"page":6},"Concepts","\u002Fconcepts","3.concepts",[109,114,119],{"title":110,"path":111,"stem":112,"icon":113},"Types of code clones","\u002Fconcepts\u002Fclone-types","3.concepts\u002F1.clone-types","i-lucide-copy",{"title":115,"path":116,"stem":117,"icon":118},"How detection works","\u002Fconcepts\u002Fhow-detection-works","3.concepts\u002F2.how-detection-works","i-lucide-scan-search",{"title":120,"path":121,"stem":122,"icon":123},"The health score","\u002Fconcepts\u002Fhealth-score","3.concepts\u002F3.health-score","i-lucide-heart-pulse",{"title":125,"icon":6,"path":126,"stem":127,"children":128,"page":6},"Reference","\u002Freference","4.reference",[129,134,139,144,181,186,190],{"title":130,"path":131,"stem":132,"icon":133},"CLI options","\u002Freference\u002Fcli","4.reference\u002F1.cli","i-lucide-terminal",{"title":135,"path":136,"stem":137,"icon":138},"The config file","\u002Freference\u002Fconfig-file","4.reference\u002F2.config-file","i-lucide-settings",{"title":140,"path":141,"stem":142,"icon":143},"Exit codes","\u002Freference\u002Fexit-codes","4.reference\u002F3.exit-codes","i-lucide-log-out",{"title":145,"icon":6,"path":146,"stem":147,"children":148},"Reporters","\u002Freference\u002Freporters","4.reference\u002F4.reporters\u002Findex",[149,151,156,161,166,171,176],{"title":145,"path":146,"stem":147,"icon":150},"i-lucide-file-chart-column",{"title":152,"path":153,"stem":154,"icon":155},"HTML reporter","\u002Freference\u002Freporters\u002Fhtml","4.reference\u002F4.reporters\u002F2.html","i-lucide-file-code",{"title":157,"path":158,"stem":159,"icon":160},"JSON reporter","\u002Freference\u002Freporters\u002Fjson","4.reference\u002F4.reporters\u002F3.json","i-lucide-braces",{"title":162,"path":163,"stem":164,"icon":165},"Badge reporter","\u002Freference\u002Freporters\u002Fbadge","4.reference\u002F4.reporters\u002F4.badge","i-lucide-award",{"title":167,"path":168,"stem":169,"icon":170},"SARIF reporter","\u002Freference\u002Freporters\u002Fsarif","4.reference\u002F4.reporters\u002F5.sarif","i-lucide-shield-check",{"title":172,"path":173,"stem":174,"icon":175},"CodeClimate reporter","\u002Freference\u002Freporters\u002Fcodeclimate","4.reference\u002F4.reporters\u002F6.codeclimate","i-lucide-flag-triangle-right",{"title":177,"path":178,"stem":179,"icon":180},"OpenMetrics reporter","\u002Freference\u002Freporters\u002Fopenmetrics","4.reference\u002F4.reporters\u002F7.openmetrics","i-lucide-activity",{"title":182,"path":183,"stem":184,"icon":185},"Supported formats","\u002Freference\u002Fsupported-formats","4.reference\u002F5.supported-formats","i-lucide-languages",{"title":187,"path":188,"stem":189,"icon":38},"GitHub Action","\u002Freference\u002Fgithub-action","4.reference\u002F6.github-action",{"title":191,"path":192,"stem":193,"icon":68},"Rust crates","\u002Freference\u002Frust-crates","4.reference\u002F7.rust-crates",{"title":195,"icon":6,"path":196,"stem":197,"children":198,"page":6},"Project","\u002Fproject","5.project",[199,204,209,214,239,244],{"title":200,"path":201,"stem":202,"icon":203},"Changelog","\u002Fproject\u002Fchangelog","5.project\u002F1.changelog","i-lucide-scroll-text",{"title":205,"path":206,"stem":207,"icon":208},"Migrating from v4 to v5","\u002Fproject\u002Fmigration","5.project\u002F2.migration","i-lucide-arrow-right-left",{"title":210,"path":211,"stem":212,"icon":213},"jscpd v4 (TypeScript)","\u002Fproject\u002Fv4","5.project\u002F3.v4","i-simple-icons-typescript",{"title":215,"icon":6,"path":216,"stem":217,"children":218},"Benchmarks","\u002Fproject\u002Fbenchmarks","5.project\u002F4.benchmarks\u002Findex",[219,221,226,230,235],{"title":215,"path":216,"stem":217,"icon":220},"i-lucide-gauge",{"title":222,"path":223,"stem":224,"icon":225},"Detection Speed","\u002Fproject\u002Fbenchmarks\u002Fdetection-speed","5.project\u002F4.benchmarks\u002F2.detection-speed","i-lucide-zap",{"title":227,"path":228,"stem":229,"icon":58},"Multi-Format Detection","\u002Fproject\u002Fbenchmarks\u002Fcross-format","5.project\u002F4.benchmarks\u002F3.cross-format",{"title":231,"path":232,"stem":233,"icon":234},"AI Token Efficiency","\u002Fproject\u002Fbenchmarks\u002Fai-token-efficiency","5.project\u002F4.benchmarks\u002F4.ai-token-efficiency","i-lucide-sparkles",{"title":236,"path":237,"stem":238,"icon":93},"Embedding Models","\u002Fproject\u002Fbenchmarks\u002Fembedding-models","5.project\u002F4.benchmarks\u002F5.embedding-models",{"title":240,"path":241,"stem":242,"icon":243},"Research","\u002Fproject\u002Fresearch","5.project\u002F5.research","i-lucide-graduation-cap",{"title":245,"path":246,"stem":247,"icon":248},"Articles","\u002Fproject\u002Farticles","5.project\u002F6.articles","i-lucide-newspaper",{"id":250,"title":251,"authors":252,"body":254,"date":1671,"description":1672,"extension":1673,"meta":1674,"navigation":6,"path":1675,"publishedAt":1676,"seo":1677,"stem":1679,"tags":1680,"__hash__":1682},"blog\u002Fblog\u002Fjscpd-5-4-2.md","What's new in jscpd 5.4.2",[253],"kucherenko",{"type":255,"value":256,"toc":1662},"minimark",[257,274,279,282,289,501,516,519,586,593,639,885,903,914,918,924,986,1001,1004,1080,1123,1140,1144,1149,1226,1252,1304,1318,1322,1334,1342,1366,1370,1403,1465,1512,1516,1600,1604,1645,1658],[258,259,260,261,265,266,273],"p",{},"5.4.2 adds boundary rules, makes the search for renamed copies strict about names, puts the health score into ",[262,263,264],"code",{},"--history"," and knows 73 more formats. Every example below runs on a demo folder from the ",[267,268,272],"a",{"href":269,"rel":270},"https:\u002F\u002Fgithub.com\u002Fkucherenko\u002Fjscpd\u002Ftree\u002Fmaster\u002Ffixtures",[271],"nofollow","fixtures of the repository",", so you can repeat it on your machine.",[275,276,278],"h2",{"id":277},"keep-the-code-of-a-module-inside-it","Keep the code of a module inside it",[258,280,281],{},"Some code belongs in one place. In a shop that also gives loans, the backend calculates every amount it charges. A discount or a monthly payment that the web app calculates too is a second version of a backend function, and when the formula changes on the backend, the copy keeps the old one. Import linters such as dependency-cruiser or ArchUnit check who imports whom. A copied function imports nothing, so they don't see it.",[258,283,284,285,288],{},"A boundary rule in ",[262,286,287],{},".jscpd.json"," names the code to protect and the places its copies must stay out of:",[290,291,296],"pre",{"className":292,"code":293,"language":294,"meta":295,"style":295},"language-json shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","{\n  \"boundaries\": [\n    {\n      \"name\": \"money\",\n      \"code\": [\"backend\u002Fpricing\", \"backend\u002Floans\u002FInterest.kt#monthlyPayment\"],\n      \"notIn\": [\"web\u002F**\"],\n      \"allow\": [\"web\u002Fe2e\u002F**\"],\n      \"thresholds\": { \"minTokens\": 30, \"similarity\": 0.7 }\n    }\n  ]\n}\n","json","",[262,297,298,307,326,332,358,392,415,438,483,489,495],{"__ignoreMap":295},[299,300,303],"span",{"class":301,"line":302},"line",1,[299,304,306],{"class":305},"sMK4o","{\n",[299,308,310,313,317,320,323],{"class":301,"line":309},2,[299,311,312],{"class":305},"  \"",[299,314,316],{"class":315},"spNyl","boundaries",[299,318,319],{"class":305},"\"",[299,321,322],{"class":305},":",[299,324,325],{"class":305}," [\n",[299,327,329],{"class":301,"line":328},3,[299,330,331],{"class":305},"    {\n",[299,333,335,338,342,344,346,349,353,355],{"class":301,"line":334},4,[299,336,337],{"class":305},"      \"",[299,339,341],{"class":340},"sBMFI","name",[299,343,319],{"class":305},[299,345,322],{"class":305},[299,347,348],{"class":305}," \"",[299,350,352],{"class":351},"sfazB","money",[299,354,319],{"class":305},[299,356,357],{"class":305},",\n",[299,359,361,363,365,367,369,372,374,377,379,382,384,387,389],{"class":301,"line":360},5,[299,362,337],{"class":305},[299,364,262],{"class":340},[299,366,319],{"class":305},[299,368,322],{"class":305},[299,370,371],{"class":305}," [",[299,373,319],{"class":305},[299,375,376],{"class":351},"backend\u002Fpricing",[299,378,319],{"class":305},[299,380,381],{"class":305},",",[299,383,348],{"class":305},[299,385,386],{"class":351},"backend\u002Floans\u002FInterest.kt#monthlyPayment",[299,388,319],{"class":305},[299,390,391],{"class":305},"],\n",[299,393,395,397,400,402,404,406,408,411,413],{"class":301,"line":394},6,[299,396,337],{"class":305},[299,398,399],{"class":340},"notIn",[299,401,319],{"class":305},[299,403,322],{"class":305},[299,405,371],{"class":305},[299,407,319],{"class":305},[299,409,410],{"class":351},"web\u002F**",[299,412,319],{"class":305},[299,414,391],{"class":305},[299,416,418,420,423,425,427,429,431,434,436],{"class":301,"line":417},7,[299,419,337],{"class":305},[299,421,422],{"class":340},"allow",[299,424,319],{"class":305},[299,426,322],{"class":305},[299,428,371],{"class":305},[299,430,319],{"class":305},[299,432,433],{"class":351},"web\u002Fe2e\u002F**",[299,435,319],{"class":305},[299,437,391],{"class":305},[299,439,441,443,446,448,450,453,455,459,461,463,466,468,470,473,475,477,480],{"class":301,"line":440},8,[299,442,337],{"class":305},[299,444,445],{"class":340},"thresholds",[299,447,319],{"class":305},[299,449,322],{"class":305},[299,451,452],{"class":305}," {",[299,454,348],{"class":305},[299,456,458],{"class":457},"sbssI","minTokens",[299,460,319],{"class":305},[299,462,322],{"class":305},[299,464,465],{"class":457}," 30",[299,467,381],{"class":305},[299,469,348],{"class":305},[299,471,472],{"class":457},"similarity",[299,474,319],{"class":305},[299,476,322],{"class":305},[299,478,479],{"class":457}," 0.7",[299,481,482],{"class":305}," }\n",[299,484,486],{"class":301,"line":485},9,[299,487,488],{"class":305},"    }\n",[299,490,492],{"class":301,"line":491},10,[299,493,494],{"class":305},"  ]\n",[299,496,498],{"class":301,"line":497},11,[299,499,500],{"class":305},"}\n",[258,502,503,505,506,509,510,512,513,515],{},[262,504,262],{}," takes a folder, a file, a glob, or one method as ",[262,507,508],{},"path#name",". ",[262,511,422],{}," lets the end-to-end tests keep their copy of the discount, and ",[262,514,445],{}," looks for smaller and looser copies than the clone report does, because for money code a missed copy costs more than a false alarm.",[258,517,518],{},"Every clone run checks the rules. The demo web app has three copies:",[290,520,524],{"className":521,"code":522,"language":523,"meta":295,"style":295},"language-bash shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","jscpd --config fixtures\u002Fboundaries-demo\u002F.jscpd.json fixtures\u002Fboundaries-demo\n# Boundaries:\n# money: 3 copies outside the boundary\n#   web\u002Fsrc\u002Fcheckout\u002Fdiscount.ts:8-17 applyDiscount\n#     copy of backend\u002Fpricing\u002Fdiscount.ts:6-15 applyDiscount (exact, 10 lines)\n#   web\u002Fsrc\u002Fcheckout\u002Ftax.ts:6-15 estimateTax\n#     looks like backend\u002Fpricing\u002Ftax.ts:5-13 salesTax (similar (ast) ~0.84)\n#   web\u002Fsrc\u002Floan\u002Fpayment.ts:4-14 monthlyPayment\n#     looks like backend\u002Floans\u002FInterest.kt:8-15 monthlyPayment (semantic ~0.82)\n# ERROR: jscpd found 3 copies of protected code outside their boundaries (money: 3)\n","bash",[262,525,526,540,546,551,556,561,566,571,576,581],{"__ignoreMap":295},[299,527,528,531,534,537],{"class":301,"line":302},[299,529,530],{"class":340},"jscpd",[299,532,533],{"class":351}," --config",[299,535,536],{"class":351}," fixtures\u002Fboundaries-demo\u002F.jscpd.json",[299,538,539],{"class":351}," fixtures\u002Fboundaries-demo\n",[299,541,542],{"class":301,"line":309},[299,543,545],{"class":544},"sHwdD","# Boundaries:\n",[299,547,548],{"class":301,"line":328},[299,549,550],{"class":544},"# money: 3 copies outside the boundary\n",[299,552,553],{"class":301,"line":334},[299,554,555],{"class":544},"#   web\u002Fsrc\u002Fcheckout\u002Fdiscount.ts:8-17 applyDiscount\n",[299,557,558],{"class":301,"line":360},[299,559,560],{"class":544},"#     copy of backend\u002Fpricing\u002Fdiscount.ts:6-15 applyDiscount (exact, 10 lines)\n",[299,562,563],{"class":301,"line":394},[299,564,565],{"class":544},"#   web\u002Fsrc\u002Fcheckout\u002Ftax.ts:6-15 estimateTax\n",[299,567,568],{"class":301,"line":417},[299,569,570],{"class":544},"#     looks like backend\u002Fpricing\u002Ftax.ts:5-13 salesTax (similar (ast) ~0.84)\n",[299,572,573],{"class":301,"line":440},[299,574,575],{"class":544},"#   web\u002Fsrc\u002Floan\u002Fpayment.ts:4-14 monthlyPayment\n",[299,577,578],{"class":301,"line":485},[299,579,580],{"class":544},"#     looks like backend\u002Floans\u002FInterest.kt:8-15 monthlyPayment (semantic ~0.82)\n",[299,582,583],{"class":301,"line":491},[299,584,585],{"class":544},"# ERROR: jscpd found 3 copies of protected code outside their boundaries (money: 3)\n",[258,587,588,589,592],{},"jscpd found each copy a different way. The discount is copy-paste, a token clone. The tax has other names and a debug line, and the structure comparison of ",[262,590,591],{},"--similarity"," finds it. The monthly payment is the Kotlin method written again in TypeScript:",[290,594,598],{"className":595,"code":596,"language":597,"meta":295,"style":295},"language-kotlin shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","fun monthlyPayment(principalCents: Long, annualRatePercent: Double, months: Int): Long {\n    if (months \u003C= 0) return principalCents\n    val monthlyRate = annualRatePercent \u002F 100.0 \u002F 12.0\n    if (monthlyRate == 0.0) return principalCents \u002F months\n    val factor = (1 + monthlyRate).pow(months)\n    val payment = principalCents * monthlyRate * factor \u002F (factor - 1)\n    return payment.roundToLong()\n}\n","kotlin",[262,599,600,605,610,615,620,625,630,635],{"__ignoreMap":295},[299,601,602],{"class":301,"line":302},[299,603,604],{},"fun monthlyPayment(principalCents: Long, annualRatePercent: Double, months: Int): Long {\n",[299,606,607],{"class":301,"line":309},[299,608,609],{},"    if (months \u003C= 0) return principalCents\n",[299,611,612],{"class":301,"line":328},[299,613,614],{},"    val monthlyRate = annualRatePercent \u002F 100.0 \u002F 12.0\n",[299,616,617],{"class":301,"line":334},[299,618,619],{},"    if (monthlyRate == 0.0) return principalCents \u002F months\n",[299,621,622],{"class":301,"line":360},[299,623,624],{},"    val factor = (1 + monthlyRate).pow(months)\n",[299,626,627],{"class":301,"line":394},[299,628,629],{},"    val payment = principalCents * monthlyRate * factor \u002F (factor - 1)\n",[299,631,632],{"class":301,"line":417},[299,633,634],{},"    return payment.roundToLong()\n",[299,636,637],{"class":301,"line":440},[299,638,500],{},[290,640,644],{"className":641,"code":642,"language":643,"meta":295,"style":295},"language-ts shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","export function monthlyPayment(principal: number, yearlyRate: number, termMonths: number): number {\n  if (termMonths \u003C= 0) {\n    return principal;\n  }\n  const r = yearlyRate \u002F 100 \u002F 12;\n  if (r === 0) {\n    return principal \u002F termMonths;\n  }\n  const growth = Math.pow(1 + r, termMonths);\n  return Math.round((principal * r * growth) \u002F (growth - 1));\n}\n","ts",[262,645,646,697,721,732,737,763,781,793,797,834,881],{"__ignoreMap":295},[299,647,648,652,655,659,662,666,668,671,673,676,678,680,682,685,687,689,692,694],{"class":301,"line":302},[299,649,651],{"class":650},"s7zQu","export",[299,653,654],{"class":315}," function",[299,656,658],{"class":657},"s2Zo4"," monthlyPayment",[299,660,661],{"class":305},"(",[299,663,665],{"class":664},"sHdIc","principal",[299,667,322],{"class":305},[299,669,670],{"class":340}," number",[299,672,381],{"class":305},[299,674,675],{"class":664}," yearlyRate",[299,677,322],{"class":305},[299,679,670],{"class":340},[299,681,381],{"class":305},[299,683,684],{"class":664}," termMonths",[299,686,322],{"class":305},[299,688,670],{"class":340},[299,690,691],{"class":305},"):",[299,693,670],{"class":340},[299,695,696],{"class":305}," {\n",[299,698,699,702,706,710,713,716,719],{"class":301,"line":309},[299,700,701],{"class":650},"  if",[299,703,705],{"class":704},"swJcz"," (",[299,707,709],{"class":708},"sTEyZ","termMonths",[299,711,712],{"class":305}," \u003C=",[299,714,715],{"class":457}," 0",[299,717,718],{"class":704},") ",[299,720,306],{"class":305},[299,722,723,726,729],{"class":301,"line":328},[299,724,725],{"class":650},"    return",[299,727,728],{"class":708}," principal",[299,730,731],{"class":305},";\n",[299,733,734],{"class":301,"line":334},[299,735,736],{"class":305},"  }\n",[299,738,739,742,745,748,750,753,756,758,761],{"class":301,"line":360},[299,740,741],{"class":315},"  const",[299,743,744],{"class":708}," r",[299,746,747],{"class":305}," =",[299,749,675],{"class":708},[299,751,752],{"class":305}," \u002F",[299,754,755],{"class":457}," 100",[299,757,752],{"class":305},[299,759,760],{"class":457}," 12",[299,762,731],{"class":305},[299,764,765,767,769,772,775,777,779],{"class":301,"line":394},[299,766,701],{"class":650},[299,768,705],{"class":704},[299,770,771],{"class":708},"r",[299,773,774],{"class":305}," ===",[299,776,715],{"class":457},[299,778,718],{"class":704},[299,780,306],{"class":305},[299,782,783,785,787,789,791],{"class":301,"line":417},[299,784,725],{"class":650},[299,786,728],{"class":708},[299,788,752],{"class":305},[299,790,684],{"class":708},[299,792,731],{"class":305},[299,794,795],{"class":301,"line":440},[299,796,736],{"class":305},[299,798,799,801,804,806,809,812,815,817,820,823,825,827,829,832],{"class":301,"line":485},[299,800,741],{"class":315},[299,802,803],{"class":708}," growth",[299,805,747],{"class":305},[299,807,808],{"class":708}," Math",[299,810,811],{"class":305},".",[299,813,814],{"class":657},"pow",[299,816,661],{"class":704},[299,818,819],{"class":457},"1",[299,821,822],{"class":305}," +",[299,824,744],{"class":708},[299,826,381],{"class":305},[299,828,684],{"class":708},[299,830,831],{"class":704},")",[299,833,731],{"class":305},[299,835,836,839,841,843,846,849,851,854,856,858,860,862,865,867,870,873,876,879],{"class":301,"line":491},[299,837,838],{"class":650},"  return",[299,840,808],{"class":708},[299,842,811],{"class":305},[299,844,845],{"class":657},"round",[299,847,848],{"class":704},"((",[299,850,665],{"class":708},[299,852,853],{"class":305}," *",[299,855,744],{"class":708},[299,857,853],{"class":305},[299,859,803],{"class":708},[299,861,718],{"class":704},[299,863,864],{"class":305},"\u002F",[299,866,705],{"class":704},[299,868,869],{"class":708},"growth",[299,871,872],{"class":305}," -",[299,874,875],{"class":457}," 1",[299,877,878],{"class":704},"))",[299,880,731],{"class":305},[299,882,883],{"class":301,"line":497},[299,884,500],{"class":305},[258,886,887,888,891,892,509,895,898,899,902],{},"Tokens and syntax trees don't match across Kotlin and TypeScript, so the rules pair functions the way ",[262,889,890],{},"--compare"," does, with the embedding model of ",[262,893,894],{},"--semantic",[262,896,897],{},"jscpd --semantic-download"," gets it once, 548 MB. Without the model jscpd prints a warning and checks the rules without it, and here it finds the first two copies. A rule with ",[262,900,901],{},"\"disable\": [\"semantic\"]"," needs no model at all.",[258,904,905,906,909,910,913],{},"The run exits with code 1, so a rule is a CI gate as it is. The console, ai and json reporters show the violations. Baselines, ",[262,907,908],{},"--changed",", SARIF, the language server and the MCP server don't know about the rules yet, they come next. The ",[267,911,912],{"href":101},"boundaries guide"," has every key, the errors and warnings, and a CI job.",[275,915,917],{"id":916},"renamed-copies-that-are-really-renamed","Renamed copies that are really renamed",[258,919,920,923],{},[262,921,922],{},"--ignore-identifiers"," used to turn every name into the same placeholder, so any name matched any other. These two functions came out as a clone:",[290,925,929],{"className":926,"code":927,"language":928,"meta":295,"style":295},"language-python shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","def compute(a, b, c):\n    d = a + b * c\n    e = d - a \u002F b\n    f = e * c + d\n    ...\n\ndef evaluate(x, y, z):\n    d = x + x * x\n    e = d - x \u002F x\n    f = e * x + d\n    ...\n","python",[262,930,931,936,941,946,951,956,962,967,972,977,982],{"__ignoreMap":295},[299,932,933],{"class":301,"line":302},[299,934,935],{},"def compute(a, b, c):\n",[299,937,938],{"class":301,"line":309},[299,939,940],{},"    d = a + b * c\n",[299,942,943],{"class":301,"line":328},[299,944,945],{},"    e = d - a \u002F b\n",[299,947,948],{"class":301,"line":334},[299,949,950],{},"    f = e * c + d\n",[299,952,953],{"class":301,"line":360},[299,954,955],{},"    ...\n",[299,957,958],{"class":301,"line":394},[299,959,961],{"emptyLinePlaceholder":960},true,"\n",[299,963,964],{"class":301,"line":417},[299,965,966],{},"def evaluate(x, y, z):\n",[299,968,969],{"class":301,"line":440},[299,970,971],{},"    d = x + x * x\n",[299,973,974],{"class":301,"line":485},[299,975,976],{},"    e = d - x \u002F x\n",[299,978,979],{"class":301,"line":491},[299,980,981],{},"    f = e * x + d\n",[299,983,984],{"class":301,"line":497},[299,985,955],{},[258,987,988,989,994,995,1000],{},"They have the same shape, and the data flows through them another way. ",[267,990,993],{"href":991,"rel":992},"https:\u002F\u002Fgithub.com\u002Fsoodoku",[271],"@soodoku"," opened ",[267,996,999],{"href":997,"rel":998},"https:\u002F\u002Fgithub.com\u002Fkucherenko\u002Fjscpd\u002Fissues\u002F1217",[271],"an issue"," about it, with numbers from Django and scikit-learn. I checked 24 projects in 11 languages: 6,942 of their 14,501 renamed clones had no one-to-one mapping between the names. Most of them were import lists, a list matched with itself shifted by a line, or statements that only look alike.",[258,1002,1003],{},"Now a name on one side has to stand for the same name on the other side every time, and two names can't stand for one:",[290,1005,1007],{"className":521,"code":1006,"language":523,"meta":295,"style":295},"jscpd fixtures\u002Ftype2-demo\u002Fconsistent\u002Fpython --ignore-identifiers=blind\n# Clone found (python, renamed)\n#  - one.py [1:1 - 8:13] (8 lines, 54 tokens)\n#    three.py [1:1 - 8:13]\n# Clone found (python, renamed)\n#  - one.py [1:1 - 8:13] (8 lines, 54 tokens)\n#    two.py [1:1 - 8:13]\n# Found 2 clones.\njscpd fixtures\u002Ftype2-demo\u002Fconsistent\u002Fpython --ignore-identifiers\n# Clone found (python, renamed)\n#  - one.py [1:1 - 8:13] (8 lines, 54 tokens)\n#    two.py [1:1 - 8:13]\n# Found 1 clones.\n",[262,1008,1009,1019,1024,1029,1034,1038,1042,1047,1052,1061,1065,1069,1074],{"__ignoreMap":295},[299,1010,1011,1013,1016],{"class":301,"line":302},[299,1012,530],{"class":340},[299,1014,1015],{"class":351}," fixtures\u002Ftype2-demo\u002Fconsistent\u002Fpython",[299,1017,1018],{"class":351}," --ignore-identifiers=blind\n",[299,1020,1021],{"class":301,"line":309},[299,1022,1023],{"class":544},"# Clone found (python, renamed)\n",[299,1025,1026],{"class":301,"line":328},[299,1027,1028],{"class":544},"#  - one.py [1:1 - 8:13] (8 lines, 54 tokens)\n",[299,1030,1031],{"class":301,"line":334},[299,1032,1033],{"class":544},"#    three.py [1:1 - 8:13]\n",[299,1035,1036],{"class":301,"line":360},[299,1037,1023],{"class":544},[299,1039,1040],{"class":301,"line":394},[299,1041,1028],{"class":544},[299,1043,1044],{"class":301,"line":417},[299,1045,1046],{"class":544},"#    two.py [1:1 - 8:13]\n",[299,1048,1049],{"class":301,"line":440},[299,1050,1051],{"class":544},"# Found 2 clones.\n",[299,1053,1054,1056,1058],{"class":301,"line":485},[299,1055,530],{"class":340},[299,1057,1015],{"class":351},[299,1059,1060],{"class":351}," --ignore-identifiers\n",[299,1062,1063],{"class":301,"line":491},[299,1064,1023],{"class":544},[299,1066,1067],{"class":301,"line":497},[299,1068,1028],{"class":544},[299,1070,1072],{"class":301,"line":1071},12,[299,1073,1046],{"class":544},[299,1075,1077],{"class":301,"line":1076},13,[299,1078,1079],{"class":544},"# Found 1 clones.\n",[258,1081,1082,1085,1086,1089,1090,1093,1094,1097,1098,1101,1102,1105,1106,1109,1110,1113,1114,1116,1117,1119,1120,811],{},[262,1083,1084],{},"two.py"," renames every name of ",[262,1087,1088],{},"one.py"," one for one, ",[262,1091,1092],{},"three.py"," is ",[262,1095,1096],{},"evaluate"," from above, and ",[262,1099,1100],{},"=blind"," is the old matching. A member is a name apart from a local of the same text, so a constructor that sets ",[262,1103,1104],{},"this.url = url"," still matches its copy that sets ",[262,1107,1108],{},"this.url = address",": the field ",[262,1111,1112],{},"url"," stands for the field ",[262,1115,1112],{},", and the parameter ",[262,1118,1112],{}," for ",[262,1121,1122],{},"address",[258,1124,1125,1126,1128,1129,1131,1132,1135,1136,1139],{},"After the update ",[262,1127,922],{}," reports fewer renamed clones, and some of them are shorter, because jscpd cuts a clone where a name starts to stand for two. A baseline that 5.4 made with ",[262,1130,922],{}," holds the old clones. Keep ",[262,1133,1134],{},"--ignore-identifiers=blind"," with it or update it, otherwise ",[262,1137,1138],{},"--fail-on-new-clones"," counts the cut clones as new.",[275,1141,1143],{"id":1142},"the-same-rule-in-similarity","The same rule in --similarity",[258,1145,1146,1148],{},[262,1147,591],{}," had the same blind spot, every local name became one marker. Now the fingerprint of a subtree also records how its names repeat. Two functions from the demo:",[290,1150,1152],{"className":926,"code":1151,"language":928,"meta":295,"style":295},"def clamp_low(values, floor):\n    result = []\n    for value in values:\n        if value \u003C floor:\n            value = floor\n        result.append(value)\n    return result\n\ndef clamp_high(values, ceiling):\n    result = []\n    for value in values:\n        if ceiling \u003C value:\n            value = ceiling\n        result.append(value)\n    return result\n",[262,1153,1154,1159,1164,1169,1174,1179,1184,1189,1193,1198,1202,1206,1211,1216,1221],{"__ignoreMap":295},[299,1155,1156],{"class":301,"line":302},[299,1157,1158],{},"def clamp_low(values, floor):\n",[299,1160,1161],{"class":301,"line":309},[299,1162,1163],{},"    result = []\n",[299,1165,1166],{"class":301,"line":328},[299,1167,1168],{},"    for value in values:\n",[299,1170,1171],{"class":301,"line":334},[299,1172,1173],{},"        if value \u003C floor:\n",[299,1175,1176],{"class":301,"line":360},[299,1177,1178],{},"            value = floor\n",[299,1180,1181],{"class":301,"line":394},[299,1182,1183],{},"        result.append(value)\n",[299,1185,1186],{"class":301,"line":417},[299,1187,1188],{},"    return result\n",[299,1190,1191],{"class":301,"line":440},[299,1192,961],{"emptyLinePlaceholder":960},[299,1194,1195],{"class":301,"line":485},[299,1196,1197],{},"def clamp_high(values, ceiling):\n",[299,1199,1200],{"class":301,"line":491},[299,1201,1163],{},[299,1203,1204],{"class":301,"line":497},[299,1205,1168],{},[299,1207,1208],{"class":301,"line":1071},[299,1209,1210],{},"        if ceiling \u003C value:\n",[299,1212,1213],{"class":301,"line":1076},[299,1214,1215],{},"            value = ceiling\n",[299,1217,1219],{"class":301,"line":1218},14,[299,1220,1183],{},[299,1222,1224],{"class":301,"line":1223},15,[299,1225,1188],{},[258,1227,1228,1229,1232,1233,1236,1237,1240,1241,1243,1244,1247,1248,1251],{},"They are made of the same markers, but the comparison is mirrored, so ",[262,1230,1231],{},"value"," would stand for ",[262,1234,1235],{},"ceiling"," in the ",[262,1238,1239],{},"if"," and for ",[262,1242,1231],{}," in the assignment. A renamed copy of ",[262,1245,1246],{},"clamp_low"," still scores 1.00, and ",[262,1249,1250],{},"clamp_high"," scores 0.74, under the default bar of 0.8:",[290,1253,1255],{"className":521,"code":1254,"language":523,"meta":295,"style":295},"jscpd fixtures\u002Fsimilarity-demo\u002Fnames --similarity 0.7\n# Clone found (python, similar (ast) ~0.74)\n#  - clamp_high.py [1:1 - 7:18] (7 lines, 33 tokens)\n#    clamp_low.py [1:1 - 7:18]\n# Clone found (python, similar (ast) ~1.00)\n#  - clamp_low.py [1:1 - 7:18] (7 lines, 33 tokens)\n#    limit_low.py [1:1 - 7:16]\n# Found 2 clones.\n",[262,1256,1257,1270,1275,1280,1285,1290,1295,1300],{"__ignoreMap":295},[299,1258,1259,1261,1264,1267],{"class":301,"line":302},[299,1260,530],{"class":340},[299,1262,1263],{"class":351}," fixtures\u002Fsimilarity-demo\u002Fnames",[299,1265,1266],{"class":351}," --similarity",[299,1268,1269],{"class":457}," 0.7\n",[299,1271,1272],{"class":301,"line":309},[299,1273,1274],{"class":544},"# Clone found (python, similar (ast) ~0.74)\n",[299,1276,1277],{"class":301,"line":328},[299,1278,1279],{"class":544},"#  - clamp_high.py [1:1 - 7:18] (7 lines, 33 tokens)\n",[299,1281,1282],{"class":301,"line":334},[299,1283,1284],{"class":544},"#    clamp_low.py [1:1 - 7:18]\n",[299,1286,1287],{"class":301,"line":360},[299,1288,1289],{"class":544},"# Clone found (python, similar (ast) ~1.00)\n",[299,1291,1292],{"class":301,"line":394},[299,1293,1294],{"class":544},"#  - clamp_low.py [1:1 - 7:18] (7 lines, 33 tokens)\n",[299,1296,1297],{"class":301,"line":417},[299,1298,1299],{"class":544},"#    limit_low.py [1:1 - 7:16]\n",[299,1301,1302],{"class":301,"line":440},[299,1303,1051],{"class":544},[258,1305,1306,1307,1310,1311,1314,1315,1317],{},"Fields, keyword arguments, object keys and types keep names of their own, so ",[262,1308,1309],{},"self.url = url"," still matches ",[262,1312,1313],{},"self.url = link",". On the same 24 projects 15 of 23,530 pairs fell below 0.8, and runs take 2 to 9% longer. ",[262,1316,1134],{}," brings the old scores back.",[275,1319,1321],{"id":1320},"health-in-history","Health in --history",[258,1323,1324,1326,1327,1330,1331,1333],{},[262,1325,264],{}," scans every commit of a range and prints the trend of duplication. Now every point also gets the health score that ",[262,1328,1329],{},"jscpd --health"," gives its tree, and a bare ",[262,1332,264],{}," walks the last 30 commits of the branch. The demo repository has four commits: a function, two copies of it, and then an import instead of one copy:",[290,1335,1340],{"className":1336,"code":1338,"language":1339,"meta":295},[1337],"language-text","  #  COMMIT   DATE        FILES  LINES  CLONES  DUP LINES   DUP%  CHANGE  HEALTH  CHANGE  SUBJECT\n  1  3ba9acb  2026-08-01      1     11       0          0   0.0%            74 B          initial helpers\n  2  3ac98f6  2026-08-08      2     21       1         10  47.6%   +47.6    72 B      -2  copy total() into b.js\n  3  5cec522  2026-08-15      3     31       2         20  64.5%   +16.9    70 B      -2  and again into c.js\n  4  fe3f38a  2026-08-22      2     21       1         10  47.6%   -16.9    72 B      +2  b.js imports total() instead\n  5  working  2026-10-20      2     21       1         10  47.6%       =    72 B       =  (uncommitted changes)\nTrend: +47.6 points since 3ba9acb (2026-08-01)\nHealth: -2 since 3ba9acb (2026-08-01), 74 B → 72 B: duplication 75 → 72, dead code 72 → 68, complexity 75 → 75\n","text",[262,1341,1338],{"__ignoreMap":295},[258,1343,1344,1345,1348,1349,1352,1353,1356,1357,1362,1363,811],{},"Above the table jscpd draws two bar charts, one for duplication and one for health, and the ",[262,1346,1347],{},"Health:"," line says which dimension moved the score. ",[262,1350,1351],{},"jscpd --health --history"," and ",[262,1354,1355],{},"jscpd --dashboard --history"," print the badge or the dashboard first and the series under it, in the HTML report too. The walk follows first parents now, so a merged branch counts as its merge commit. The last 30 commits of axios take about 5.4 seconds, against 4.9 without the health score. The script that builds the demo repository is in ",[267,1358,1361],{"href":1359,"rel":1360},"https:\u002F\u002Fgithub.com\u002Fkucherenko\u002Fjscpd\u002Ftree\u002Fmaster\u002Ffixtures\u002Fhistory-demo",[271],"fixtures\u002Fhistory-demo",", and the options are in the ",[267,1364,1365],{"href":71},"history guide",[275,1367,1369],{"id":1368},"dockerfiles-makefiles-and-73-more-formats","Dockerfiles, Makefiles and 73 more formats",[258,1371,1372,1373,1376,1377,1380,1381,1384,1385,1384,1388,1384,1391,1384,1393,1384,1396,1384,1399,1402],{},"94 of 224 formats had no built-in extension, a gap jscpd carried over from v4, so ",[262,1374,1375],{},"jscpd ."," skipped Dockerfiles, Makefiles, ",[262,1378,1379],{},"go.mod",", Elixir and Nix files unless you mapped them by hand. The new extensions come from GitHub Linguist, and a table of file names knows ",[262,1382,1383],{},"Dockerfile",", ",[262,1386,1387],{},"Containerfile",[262,1389,1390],{},"Makefile",[262,1392,1379],{},[262,1394,1395],{},"CMakeLists.txt",[262,1397,1398],{},"Gemfile",[262,1400,1401],{},"Jenkinsfile"," and others:",[290,1404,1406],{"className":521,"code":1405,"language":523,"meta":295,"style":295},"jscpd fixtures\u002Ffile-names-demo\n# Clone found (elixir)\n#  - lib\u002Fshop\u002Finvoice_totals.ex [4:3 - 20:4] (17 lines, 116 tokens)\n#    lib\u002Fshop\u002Frefund_totals.ex [4:3 - 20:4]\n# Clone found (docker)\n#  - services\u002Fapi\u002FDockerfile [1:1 - 13:9] (13 lines, 94 tokens)\n#    services\u002Fworker\u002FDockerfile [1:1 - 13:9]\n# Clone found (makefile)\n#  - services\u002Fapi\u002FMakefile [3:1 - 21:22] (19 lines, 104 tokens)\n#    services\u002Fworker\u002FMakefile [3:1 - 21:22]\n# Found 3 clones.\n",[262,1407,1408,1415,1420,1425,1430,1435,1440,1445,1450,1455,1460],{"__ignoreMap":295},[299,1409,1410,1412],{"class":301,"line":302},[299,1411,530],{"class":340},[299,1413,1414],{"class":351}," fixtures\u002Ffile-names-demo\n",[299,1416,1417],{"class":301,"line":309},[299,1418,1419],{"class":544},"# Clone found (elixir)\n",[299,1421,1422],{"class":301,"line":328},[299,1423,1424],{"class":544},"#  - lib\u002Fshop\u002Finvoice_totals.ex [4:3 - 20:4] (17 lines, 116 tokens)\n",[299,1426,1427],{"class":301,"line":334},[299,1428,1429],{"class":544},"#    lib\u002Fshop\u002Frefund_totals.ex [4:3 - 20:4]\n",[299,1431,1432],{"class":301,"line":360},[299,1433,1434],{"class":544},"# Clone found (docker)\n",[299,1436,1437],{"class":301,"line":394},[299,1438,1439],{"class":544},"#  - services\u002Fapi\u002FDockerfile [1:1 - 13:9] (13 lines, 94 tokens)\n",[299,1441,1442],{"class":301,"line":417},[299,1443,1444],{"class":544},"#    services\u002Fworker\u002FDockerfile [1:1 - 13:9]\n",[299,1446,1447],{"class":301,"line":440},[299,1448,1449],{"class":544},"# Clone found (makefile)\n",[299,1451,1452],{"class":301,"line":485},[299,1453,1454],{"class":544},"#  - services\u002Fapi\u002FMakefile [3:1 - 21:22] (19 lines, 104 tokens)\n",[299,1456,1457],{"class":301,"line":491},[299,1458,1459],{"class":544},"#    services\u002Fworker\u002FMakefile [3:1 - 21:22]\n",[299,1461,1462],{"class":301,"line":497},[299,1463,1464],{"class":544},"# Found 3 clones.\n",[258,1466,1467,1468,1471,1472,1474,1475,1384,1478,1481,1482,1484,1485,1488,1489,1492,1493,1496,1497,1119,1502,1507,1508,1511],{},"jscpd 5.4.1 prints ",[262,1469,1470],{},"Found 0 clones."," for the same folder. So the numbers of an existing setup can move: on 22 repositories ",[262,1473,1375],{}," picked up 1 to 5 more files each, mostly Dockerfiles, ",[262,1476,1477],{},".editorconfig",[262,1479,1480],{},".jsonc",", Makefiles and ",[262,1483,1379],{},". Check a ",[262,1486,1487],{},"--threshold"," gate after the update. A mapping of your own in ",[262,1490,1491],{},"--formats-exts"," or ",[262,1494,1495],{},"--formats-names"," still wins. Thanks to ",[267,1498,1501],{"href":1499,"rel":1500},"https:\u002F\u002Fgithub.com\u002Fpygarap",[271],"@pygarap",[267,1503,1506],{"href":1504,"rel":1505},"https:\u002F\u002Fgithub.com\u002Fkucherenko\u002Fjscpd\u002Fissues\u002F1174",[271],"the issue",". The ",[267,1509,1510],{"href":183},"supported formats"," page lists every name and extension.",[275,1513,1515],{"id":1514},"smaller-things","Smaller things",[1517,1518,1519,1535,1538,1541,1556,1566,1582,1589],"ul",{},[1520,1521,1522,1523,1526,1527,1530,1531,1534],"li",{},"The MCP server's ",[262,1524,1525],{},"check_duplication"," matched no Vue, Svelte or Astro snippet, by tokens or by meaning. It splits a snippet into blocks now, the way a scan splits a file, and each match says its ",[262,1528,1529],{},"format"," and how many ",[262,1532,1533],{},"lines"," match.",[1520,1536,1537],{},"A Markdown page whose front matter ran into a code fence crashed jscpd with exit code 134, and took the MCP and language servers down with it.",[1520,1539,1540],{},"jscpd read a Razor view out of order, the HTML first and then the C#, so a clone across that seam ended before it started.",[1520,1542,1543,1545,1546,1549,1550,1555],{},[262,1544,264],{}," failed when git's ",[262,1547,1548],{},"log.showSignature"," was on. ",[267,1551,1554],{"href":1552,"rel":1553},"https:\u002F\u002Fgithub.com\u002Fvitorcsbrito",[271],"@vitorcsbrito"," reported it with an exact reproduction.",[1520,1557,1558,1560,1561,811],{},[262,1559,890],{}," paired a function with every near-best lookalike, now a function keeps one counterpart. On Lucene and Lucene.NET the share of pairs between files of the same name went from 46% to 73%. The analysis and the numbers are from ",[267,1562,1565],{"href":1563,"rel":1564},"https:\u002F\u002Fgithub.com\u002FMysterionRise",[271],"@MysterionRise",[1520,1567,1568,1570,1571,1576,1577,811],{},[262,1569,894],{}," reported a page and the component it renders as a clone, a JSX element counts as a call now. Reported by ",[267,1572,1575],{"href":1573,"rel":1574},"https:\u002F\u002Fgithub.com\u002FRosenblad",[271],"@Rosenblad",", fixed by ",[267,1578,1581],{"href":1579,"rel":1580},"https:\u002F\u002Fgithub.com\u002Fcristianchiriac",[271],"@cristianchiriac",[1520,1583,1584,1585,1588],{},"The VS Code extension couldn't open a clone in a Markdown block, because the reports named it like ",[262,1586,1587],{},"guide.md:markup",". They name the file now.",[1520,1590,1591,1592,1595,1596,1599],{},"Every repository counted the hook samples from ",[262,1593,1594],{},".git\u002Fhooks",", nine bash files and one perl file. jscpd skips ",[262,1597,1598],{},".git"," now.",[275,1601,1603],{"id":1602},"update","Update",[290,1605,1607],{"className":521,"code":1606,"language":523,"meta":295,"style":295},"curl -fsSL https:\u002F\u002Fjscpd.dev\u002Finstall.sh | bash\n# or\nnpm i -g jscpd@5\n",[262,1608,1609,1626,1631],{"__ignoreMap":295},[299,1610,1611,1614,1617,1620,1623],{"class":301,"line":302},[299,1612,1613],{"class":340},"curl",[299,1615,1616],{"class":351}," -fsSL",[299,1618,1619],{"class":351}," https:\u002F\u002Fjscpd.dev\u002Finstall.sh",[299,1621,1622],{"class":305}," |",[299,1624,1625],{"class":340}," bash\n",[299,1627,1628],{"class":301,"line":309},[299,1629,1630],{"class":544},"# or\n",[299,1632,1633,1636,1639,1642],{"class":301,"line":328},[299,1634,1635],{"class":340},"npm",[299,1637,1638],{"class":351}," i",[299,1640,1641],{"class":351}," -g",[299,1643,1644],{"class":351}," jscpd@5\n",[258,1646,1647,1648,1651,1652,1657],{},"The full list is in the ",[267,1649,1650],{"href":201},"changelog"," and in the ",[267,1653,1656],{"href":1654,"rel":1655},"https:\u002F\u002Fgithub.com\u002Fkucherenko\u002Fjscpd\u002Freleases\u002Ftag\u002Fv5.4.2",[271],"release notes",". Thanks to everyone who sent an issue, a number or a fix for this release!",[1659,1660,1661],"style",{},"html pre.shiki code .sMK4o, html code.shiki .sMK4o{--shiki-light:#39ADB5;--shiki-default:#89DDFF;--shiki-dark:#89DDFF}html pre.shiki code .spNyl, html code.shiki .spNyl{--shiki-light:#9C3EDA;--shiki-default:#C792EA;--shiki-dark:#C792EA}html pre.shiki code .sBMFI, html code.shiki .sBMFI{--shiki-light:#E2931D;--shiki-default:#FFCB6B;--shiki-dark:#FFCB6B}html pre.shiki code .sfazB, html code.shiki .sfazB{--shiki-light:#91B859;--shiki-default:#C3E88D;--shiki-dark:#C3E88D}html pre.shiki code .sbssI, html code.shiki .sbssI{--shiki-light:#F76D47;--shiki-default:#F78C6C;--shiki-dark:#F78C6C}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sHwdD, html code.shiki .sHwdD{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#546E7A;--shiki-default-font-style:italic;--shiki-dark:#676E95;--shiki-dark-font-style:italic}html pre.shiki code .s7zQu, html code.shiki .s7zQu{--shiki-light:#39ADB5;--shiki-light-font-style:italic;--shiki-default:#89DDFF;--shiki-default-font-style:italic;--shiki-dark:#89DDFF;--shiki-dark-font-style:italic}html pre.shiki code .s2Zo4, html code.shiki .s2Zo4{--shiki-light:#6182B8;--shiki-default:#82AAFF;--shiki-dark:#82AAFF}html pre.shiki code .sHdIc, html code.shiki .sHdIc{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#EEFFFF;--shiki-default-font-style:italic;--shiki-dark:#BABED8;--shiki-dark-font-style:italic}html pre.shiki code .swJcz, html code.shiki .swJcz{--shiki-light:#E53935;--shiki-default:#F07178;--shiki-dark:#F07178}html pre.shiki code .sTEyZ, html code.shiki .sTEyZ{--shiki-light:#90A4AE;--shiki-default:#EEFFFF;--shiki-dark:#BABED8}",{"title":295,"searchDepth":309,"depth":309,"links":1663},[1664,1665,1666,1667,1668,1669,1670],{"id":277,"depth":309,"text":278},{"id":916,"depth":309,"text":917},{"id":1142,"depth":309,"text":1143},{"id":1320,"depth":309,"text":1321},{"id":1368,"depth":309,"text":1369},{"id":1514,"depth":309,"text":1515},{"id":1602,"depth":309,"text":1603},"2026-10-20","Boundary rules that keep the code of a module inside it, renamed copies that pair a name with one name, the health score in --history, and Dockerfiles, Makefiles and 73 more formats. With commands and real output.","md",{},"\u002Fblog\u002Fjscpd-5-4-2","2026-10-20T15:30:00Z",{"title":251,"description":1678},"jscpd 5.4.2 explained with examples, boundary rules for code that must stay in one module, consistent renaming in --ignore-identifiers and --similarity, health in --history, and built-in file names such as Dockerfile and go.mod.","blog\u002Fjscpd-5-4-2",[1681],"release","8MYSAGKkx0xxKuoARx1DT4cfb2yyKIvlF0xxv5vURC0",{"\u002Fblog\u002Fjscpd-5-4-2":1684},{"views":309,"visits":309},1792515210259]