From e0f4e2df19e2ae12a29c91dac5396c54bb47d679 Mon Sep 17 00:00:00 2001 From: Hyan Mandian <5044101+hyanmandian@users.noreply.github.com> Date: Sat, 12 Sep 2026 10:25:25 -0300 Subject: [PATCH 01/11] chore(data): refresh the banks dataset from the current Bacen STR list MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit scripts/banks.ts now reads the CSV at its new address and skips the pseudo code 000 (Câmara de Câmbio B3, not a bank). The regenerated table goes from 348 to 463 institutions with a COMPE code: 145 codes published since the previous snapshot are now known to getBanks, getBankByCode, getBankByIspb and isValidBankAccount, and 29 codes the Central Bank no longer lists are gone. --- scripts/banks.ts | 3 +- src/_internals/constants/banks.ts | 543 +++++++++++++----- src/is-valid-bank-account/constants.ts | 28 +- .../is-valid-bank-account.test.ts | 7 +- 4 files changed, 411 insertions(+), 170 deletions(-) diff --git a/scripts/banks.ts b/scripts/banks.ts index b1e45571..2cf99f51 100644 --- a/scripts/banks.ts +++ b/scripts/banks.ts @@ -91,7 +91,8 @@ const fetchFromBacen = async (): Promise => { code === "" || name === undefined || name === "" || - !/^\d{1,3}$/.test(code) + !/^\d{1,3}$/.test(code) || + Number(code) === 0 ) { continue; } diff --git a/src/_internals/constants/banks.ts b/src/_internals/constants/banks.ts index ea39a29e..17cfcadd 100644 --- a/src/_internals/constants/banks.ts +++ b/src/_internals/constants/banks.ts @@ -19,33 +19,32 @@ export const BANKS: Bank[] = [ { code: "004", ispb: "07237373", name: "Banco do Nordeste do Brasil S.A." }, { code: "007", ispb: "33657248", name: "BANCO NACIONAL DE DESENVOLVIMENTO ECONOMICO E SOCIAL" }, { code: "010", ispb: "81723108", name: "CREDICOAMO CREDITO RURAL COOPERATIVA" }, - { code: "011", ispb: "61809182", name: "CREDIT SUISSE HEDGING-GRIFFO CORRETORA DE VALORES S.A" }, + { code: "011", ispb: "61809182", name: "UBS (BRASIL) CORRETORA DE VALORES S.A." }, { code: "012", ispb: "04866275", name: "Banco Inbursa S.A." }, { code: "014", ispb: "09274232", name: "STATE STREET BRASIL S.A. - BANCO COMERCIAL" }, { code: "015", ispb: "02819125", - name: "UBS Brasil Corretora de Câmbio, Títulos e Valores Mobiliários S.A.", + name: "UBS BB CORRETORA DE CÂMBIO, TÍTULOS E VALORES MOBILIÁRIOS S.A.", }, { code: "016", ispb: "04715685", - name: "COOPERATIVA DE CRÉDITO MÚTUO DOS DESPACHANTES DE TRÂNSITO DE SANTA CATARINA E RI", + name: "COOPERATIVA DE CRÉDITO MÚTUO DOS DESPACHANTES DE TRÂNSITO DE SANTA CATARINA E RIO GRANDE DO SUL - SICOOB CREDITRAN", }, { code: "017", ispb: "42272526", name: "BNY Mellon Banco S.A." }, { code: "018", ispb: "57839805", name: "Banco Tricury S.A." }, { code: "021", ispb: "28127603", name: "BANESTES S.A. BANCO DO ESTADO DO ESPIRITO SANTO" }, + { code: "023", ispb: "53720128", name: "CONTA SIMPLES SOCIEDADE DE CRÉDITO DIRETO S.A." }, { code: "024", ispb: "10866788", name: "Banco Bandepe S.A." }, { code: "025", ispb: "03323840", name: "Banco Alfa S.A." }, - { code: "029", ispb: "33885724", name: "Banco Itaú Consignado S.A." }, { code: "033", ispb: "90400888", name: "BANCO SANTANDER (BRASIL) S.A." }, { code: "036", ispb: "06271464", name: "Banco Bradesco BBI S.A." }, { code: "037", ispb: "04913711", name: "Banco do Estado do Pará S.A." }, { code: "040", ispb: "03609817", name: "Banco Cargill S.A." }, { code: "041", ispb: "92702067", name: "Banco do Estado do Rio Grande do Sul S.A." }, { code: "047", ispb: "13009717", name: "Banco do Estado de Sergipe S.A." }, - { code: "060", ispb: "04913129", name: "Confidence Corretora de Câmbio S.A." }, - { code: "062", ispb: "03012230", name: "Hipercard Banco Múltiplo S.A." }, + { code: "060", ispb: "04913129", name: "CONFIDENCE SOCIEDADE CORRETORA DE CAMBIO S.A." }, { code: "063", ispb: "04184779", name: "Banco Bradescard S.A." }, { code: "064", ispb: "04332281", name: "GOLDMAN SACHS DO BRASIL BANCO MULTIPLO S.A." }, { code: "065", ispb: "48795256", name: "Banco AndBank (Brasil) S.A." }, @@ -53,12 +52,12 @@ export const BANKS: Bank[] = [ { code: "069", ispb: "61033106", name: "Banco Crefisa S.A." }, { code: "070", ispb: "00000208", name: "BRB - BANCO DE BRASILIA S.A." }, { code: "074", ispb: "03017677", name: "Banco J. Safra S.A." }, - { code: "075", ispb: "03532415", name: "Banco ABN Amro S.A." }, + { code: "075", ispb: "03532415", name: "BANCO ABN AMRO CLEARING S.A." }, { code: "076", ispb: "07656500", name: "Banco KDB do Brasil S.A." }, { code: "077", ispb: "00416968", name: "Banco Inter S.A." }, { code: "078", ispb: "34111187", name: "Haitong Banco de Investimento do Brasil S.A." }, { code: "079", ispb: "09516419", name: "PICPAY BANK - BANCO MÚLTIPLO S.A" }, - { code: "080", ispb: "73622748", name: "B&T CORRETORA DE CAMBIO LTDA." }, + { code: "080", ispb: "73622748", name: "BT CORRETORA DE CÂMBIO LTDA." }, { code: "081", ispb: "10264663", name: "BancoSeguro S.A." }, { code: "082", ispb: "07679404", name: "BANCO TOPÁZIO S.A." }, { code: "083", ispb: "10690848", name: "Banco da China Brasil S.A." }, @@ -69,13 +68,12 @@ export const BANKS: Bank[] = [ { code: "093", ispb: "07945233", - name: "PÓLOCRED SOCIEDADE DE CRÉDITO AO MICROEMPREENDEDOR E À EMPRESA DE PEQUENO PORT", + name: "PÓLOCRED SOCIEDADE DE CRÉDITO AO MICROEMPREENDEDOR E À EMPRESA DE PEQUENO PORTE LTDA.", }, { code: "094", ispb: "11758741", name: "Banco Finaxis S.A." }, - { code: "095", ispb: "11703662", name: "Travelex Banco de Câmbio S.A." }, + { code: "095", ispb: "11703662", name: "BANCO TRAVELEX S.A." }, { code: "096", ispb: "00997185", name: "Banco B3 S.A." }, - { code: "097", ispb: "04632856", name: "Credisis - Central de Cooperativas de Crédito Ltda." }, - { code: "098", ispb: "78157146", name: "Credialiança Cooperativa de Crédito Rural" }, + { code: "097", ispb: "04632856", name: "CREDISIS - CENTRAL DE COOPERATIVAS DE CRÉDITO" }, { code: "099", ispb: "03046391", @@ -85,7 +83,7 @@ export const BANKS: Bank[] = [ { code: "101", ispb: "62287735", - name: "RENASCENCA DISTRIBUIDORA DE TÍTULOS E VALORES MOBILIÁRIOS LTDA", + name: "WARREN RENA DISTRIBUIDORA DE TÍTULOS E VALORES MOBILIÁRIOS LTDA", }, { code: "102", @@ -101,12 +99,6 @@ export const BANKS: Bank[] = [ name: "OLIVEIRA TRUST DISTRIBUIDORA DE TÍTULOS E VALORES MOBILIARIOS S.A.", }, { code: "113", ispb: "61723847", name: "NEON CORRETORA DE TÍTULOS E VALORES MOBILIÁRIOS S.A." }, - { - code: "114", - ispb: "05790149", - name: "Central Cooperativa de Crédito no Estado do Espírito Santo - CECOOP", - }, - { code: "117", ispb: "92856905", name: "ADVANCED CORRETORA DE CÂMBIO LTDA" }, { code: "119", ispb: "13720915", name: "Banco Western Union do Brasil S.A." }, { code: "120", ispb: "33603457", name: "BANCO RODOBENS S.A." }, { code: "121", ispb: "10664513", name: "Banco Agibank S.A." }, @@ -116,7 +108,7 @@ export const BANKS: Bank[] = [ { code: "126", ispb: "13220493", name: "BR Partners Banco de Investimento S.A." }, { code: "127", ispb: "09512542", name: "Codepe Corretora de Valores e Câmbio S.A." }, { code: "128", ispb: "19307785", name: "BRAZA BANK S.A. BANCO DE CÂMBIO" }, - { code: "129", ispb: "18520834", name: "UBS Brasil Banco de Investimento S.A." }, + { code: "129", ispb: "18520834", name: "UBS BB BANCO DE INVESTIMENTO S.A." }, { code: "130", ispb: "09313766", @@ -131,7 +123,7 @@ export const BANKS: Bank[] = [ { code: "133", ispb: "10398952", - name: "CONFEDERAÇÃO NACIONAL DAS COOPERATIVAS CENTRAIS DE CRÉDITO E ECONOMIA FAMILIAR E", + name: "CONFEDERAÇÃO NACIONAL DAS COOPERATIVAS CENTRAIS DE CRÉDITO E ECONOMIA FAMILIAR E SOLIDÁRIA - CRESOL CONFEDERAÇÃO", }, { code: "134", @@ -141,15 +133,23 @@ export const BANKS: Bank[] = [ { code: "136", ispb: "00315557", - name: "CONFEDERAÇÃO NACIONAL DAS COOPERATIVAS CENTRAIS UNICRED LTDA. - UNICRED DO BRASI", + name: "COOPERATIVA CENTRAL DE CRÉDITO UNICRED DO BRASIL - UNICRED DO BRASIL", }, - { code: "138", ispb: "10853017", name: "Get Money Corretora de Câmbio S.A." }, + { code: "138", ispb: "10853017", name: "GET MONEY SOCIEDADE CORRETORA DE CÂMBIO S.A." }, { code: "139", ispb: "55230916", name: "Intesa Sanpaolo Brasil S.A. - Banco Múltiplo" }, - { code: "140", ispb: "62169875", name: "NU INVEST CORRETORA DE VALORES S.A." }, - { code: "141", ispb: "09526594", name: "BANCO MASTER DE INVESTIMENTO S.A." }, + { + code: "140", + ispb: "62169875", + name: "NU INVESTIMENTOS S.A. - CORRETORA DE TÍTULOS E VALORES MOBILIÁRIOS", + }, + { + code: "141", + ispb: "09526594", + name: "BANCO MASTER DE INVESTIMENTO S.A. - EM LIQUIDAÇÃO EXTRAJUDICIAL", + }, { code: "142", ispb: "16944141", name: "Broker Brasil Corretora de Câmbio Ltda." }, - { code: "143", ispb: "02992317", name: "Treviso Corretora de Câmbio S.A." }, - { code: "144", ispb: "13059145", name: "BEXS BANCO DE CÂMBIO S/A" }, + { code: "143", ispb: "02992317", name: "INTEX BANK BANCO DE CÂMBIO S.A." }, + { code: "144", ispb: "13059145", name: "EBURY BANCO DE CÂMBIO S.A." }, { code: "145", ispb: "50579044", name: "LEVYCAM - CORRETORA DE CAMBIO E VALORES LTDA." }, { code: "146", ispb: "24074692", name: "GUITTA CORRETORA DE CAMBIO LTDA." }, { @@ -170,10 +170,9 @@ export const BANKS: Bank[] = [ { code: "173", ispb: "13486793", - name: "BRL Trust Distribuidora de Títulos e Valores Mobiliários S.A.", + name: "APEX GROUP DISTRIBUIDORA DE TÍTULOS E VALORES MOBILIÁRIOS S.A.", }, { code: "174", ispb: "43180355", name: "PEFISA S.A. - CRÉDITO, FINANCIAMENTO E INVESTIMENTO" }, - { code: "177", ispb: "65913436", name: "Guide Investimentos S.A. Corretora de Valores" }, { code: "180", ispb: "02685483", @@ -182,9 +181,8 @@ export const BANKS: Bank[] = [ { code: "183", ispb: "09210106", - name: "SOCRED S.A. - SOCIEDADE DE CRÉDITO AO MICROEMPREENDEDOR E À EMPRESA DE PEQUENO P", + name: "SOCRED S.A. - SOCIEDADE DE CRÉDITO AO MICROEMPREENDEDOR E À EMPRESA DE PEQUENO PORTE", }, - { code: "184", ispb: "17298092", name: "Banco Itaú BBA S.A." }, { code: "188", ispb: "33775974", @@ -198,7 +196,7 @@ export const BANKS: Bank[] = [ { code: "190", ispb: "03973814", - name: "SERVICOOP - COOPERATIVA DE CRÉDITO DOS SERVIDORES PÚBLICOS ESTADUAIS E MUNICIPAI", + name: "SERVICOOP - COOPERATIVA DE CRÉDITO DOS SERVIDORES PÚBLICOS ESTADUAIS E MUNICIPAIS DO RIO GRANDE DO SUL", }, { code: "191", @@ -208,10 +206,14 @@ export const BANKS: Bank[] = [ { code: "194", ispb: "20155248", - name: "PARMETAL DISTRIBUIDORA DE TÍTULOS E VALORES MOBILIÁRIOS LTDA", + name: "UNIDA DISTRIBUIDORA DE TÍTULOS E VALORES MOBILIÁRIOS LTDA.", }, - { code: "195", ispb: "07799277", name: "VALOR SOCIEDADE DE CRÉDITO DIRETO S.A." }, - { code: "196", ispb: "32648370", name: "FAIR CORRETORA DE CAMBIO S.A." }, + { + code: "195", + ispb: "07799277", + name: "VALOR S/A SOCIEDADE DE CRÉDITO, FINANCIAMENTO E INVESTIMENTO", + }, + { code: "196", ispb: "32648370", name: "FAIR SOCIEDADE CORRETORA DE CÂMBIO S.A." }, { code: "197", ispb: "16501555", name: "STONE INSTITUIÇÃO DE PAGAMENTO S.A." }, { code: "208", ispb: "30306294", name: "Banco BTG Pactual S.A." }, { code: "212", ispb: "92894922", name: "Banco Original S.A." }, @@ -220,14 +222,13 @@ export const BANKS: Bank[] = [ { code: "218", ispb: "71027866", name: "Banco BS2 S.A." }, { code: "222", ispb: "75647891", name: "BANCO CRÉDIT AGRICOLE BRASIL S.A." }, { code: "224", ispb: "58616418", name: "Banco Fibra S.A." }, - { code: "233", ispb: "62421979", name: "Banco Cifra S.A." }, + { code: "233", ispb: "62421979", name: "BANCO BMG SOLUÇÕES FINANCEIRAS S.A." }, { code: "237", ispb: "60746948", name: "Banco Bradesco S.A." }, { code: "241", ispb: "31597552", name: "BANCO CLASSICO S.A." }, - { code: "243", ispb: "33923798", name: "BANCO MASTER S/A" }, + { code: "243", ispb: "33923798", name: "BANCO MASTER S/A - EM LIQUIDAÇÃO EXTRAJUDICIAL" }, { code: "246", ispb: "28195667", name: "Banco ABC Brasil S.A." }, { code: "249", ispb: "61182408", name: "Banco Investcred Unibanco S.A." }, - { code: "250", ispb: "50585090", name: "BCV - BANCO DE CRÉDITO E VAREJO S.A." }, - { code: "253", ispb: "52937216", name: "Bexs Corretora de Câmbio S/A" }, + { code: "250", ispb: "50585090", name: "BANCO BMG CONSIGNADO S.A." }, { code: "254", ispb: "14388334", name: "PARANÁ BANCO S.A." }, { code: "259", ispb: "08609934", name: "MONEYCORP BANCO DE CÂMBIO S.A." }, { code: "260", ispb: "18236120", name: "NU PAGAMENTOS S.A. - INSTITUIÇÃO DE PAGAMENTO" }, @@ -235,18 +236,13 @@ export const BANKS: Bank[] = [ { code: "266", ispb: "33132044", name: "BANCO CEDULA S.A." }, { code: "268", ispb: "14511781", name: "BARI COMPANHIA HIPOTECÁRIA" }, { code: "269", ispb: "53518684", name: "BANCO HSBC S.A." }, - { code: "270", ispb: "61444949", name: "SAGITUR CORRETORA DE CÂMBIO S.A." }, { code: "271", ispb: "27842177", - name: "IB Corretora de Câmbio, Títulos e Valores Mobiliários S.A.", + name: "BPY CORRETORA DE CÂMBIO, TÍTULOS E VALORES MOBILIÁRIOS S.A.", }, { code: "272", ispb: "00250699", name: "AGK CORRETORA DE CAMBIO S.A." }, - { - code: "273", - ispb: "08253539", - name: "Cooperativa de Crédito Rural de São Miguel do Oeste - Sulcredi/São Miguel", - }, + { code: "273", ispb: "08253539", name: "COOPERATIVA DE CREDITO SULCREDI AMPLEA" }, { code: "274", ispb: "11581339", @@ -258,15 +254,10 @@ export const BANKS: Bank[] = [ ispb: "27652684", name: "Genial Investimentos Corretora de Valores Mobiliários S.A.", }, - { - code: "279", - ispb: "26563270", - name: "PRIMACREDI COOPERATIVA DE CRÉDITO DE PRIMAVERA DO LESTE", - }, { code: "280", ispb: "23862762", - name: "WILL FINANCEIRA S.A. CRÉDITO, FINANCIAMENTO E INVESTIMENTO", + name: "WILL FINANCEIRA S.A. CRÉDITO, FINANCIAMENTO E INVESTIMENTO - EM LIQUIDAÇÃO EXTRAJUDICIAL", }, { code: "281", ispb: "76461557", name: "Cooperativa de Crédito Rural Coopavel" }, { @@ -274,7 +265,6 @@ export const BANKS: Bank[] = [ ispb: "89960090", name: "RB INVESTIMENTOS DISTRIBUIDORA DE TITULOS E VALORES MOBILIARIOS LIMITADA", }, - { code: "285", ispb: "71677850", name: "FRENTE CORRETORA DE CÂMBIO S.A." }, { code: "288", ispb: "62237649", @@ -285,7 +275,7 @@ export const BANKS: Bank[] = [ { code: "292", ispb: "28650236", - name: "BS2 Distribuidora de Títulos e Valores Mobiliários S.A.", + name: "GALAPAGOS CAPITAL DISTRIBUIDORA DE TÍTULOS E VALORES MOBILIÁRIOS S.A.", }, { code: "293", @@ -293,40 +283,33 @@ export const BANKS: Bank[] = [ name: "Lastro RDV Distribuidora de Títulos e Valores Mobiliários Ltda.", }, { code: "296", ispb: "04062902", name: "OZ CORRETORA DE CÂMBIO S.A." }, - { code: "298", ispb: "17772370", name: "Vip's Corretora de Câmbio Ltda." }, + { code: "298", ispb: "17772370", name: "VIPS CORRETORA DE CÂMBIO S.A." }, { code: "299", ispb: "04814563", name: "BANCO AFINZ S.A. - BANCO MÚLTIPLO" }, { code: "300", ispb: "33042151", name: "Banco de la Nacion Argentina" }, { code: "301", ispb: "13370835", name: "DOCK INSTITUIÇÃO DE PAGAMENTO S.A." }, - { - code: "306", - ispb: "40303299", - name: "PORTOPAR DISTRIBUIDORA DE TITULOS E VALORES MOBILIARIOS LTDA.", - }, + { code: "305", ispb: "40353377", name: "FOURTRADE CORRETORA DE CÂMBIO LTDA." }, { code: "307", ispb: "03751794", name: "Terra Investimentos Distribuidora de Títulos e Valores Mobiliários Ltda.", }, - { code: "309", ispb: "14190547", name: "CAMBIONET CORRETORA DE CÂMBIO LTDA." }, { code: "310", ispb: "22610500", name: "VORTX DISTRIBUIDORA DE TITULOS E VALORES MOBILIARIOS LTDA.", }, - { code: "311", ispb: "76641497", name: "DOURADA CORRETORA DE CÂMBIO LTDA." }, { code: "312", ispb: "07693858", - name: "HSCM - SOCIEDADE DE CRÉDITO AO MICROEMPREENDEDOR E À EMPRESA DE PEQUENO PORTE LT", + name: "HSCM - SOCIEDADE DE CRÉDITO AO MICROEMPREENDEDOR E À EMPRESA DE PEQUENO PORTE LTDA.", }, - { code: "313", ispb: "16927221", name: "AMAZÔNIA CORRETORA DE CÂMBIO LTDA." }, { code: "318", ispb: "61186680", name: "Banco BMG S.A." }, { code: "319", ispb: "11495073", name: "OM DISTRIBUIDORA DE TÍTULOS E VALORES MOBILIÁRIOS LTDA" }, - { code: "320", ispb: "07450604", name: "China Construction Bank (Brasil) Banco Múltiplo S/A" }, + { code: "320", ispb: "07450604", name: "BANK OF CHINA (BRASIL) BANCO MÚLTIPLO S/A" }, { code: "321", ispb: "18188384", - name: "CREFAZ SOCIEDADE DE CRÉDITO AO MICROEMPREENDEDOR E A EMPRESA DE PEQUENO PORTE LT", + name: "CREFAZ SOCIEDADE DE CRÉDITO AO MICROEMPREENDEDOR E A EMPRESA DE PEQUENO PORTE S.A.", }, { code: "322", @@ -336,22 +319,16 @@ export const BANKS: Bank[] = [ { code: "323", ispb: "10573521", name: "MERCADO PAGO INSTITUIÇÃO DE PAGAMENTO LTDA." }, { code: "324", ispb: "21332862", name: "CARTOS SOCIEDADE DE CRÉDITO DIRETO S.A." }, { - code: "325", - ispb: "13293225", - name: "Órama Distribuidora de Títulos e Valores Mobiliários S.A.", - }, - { code: "326", ispb: "03311443", name: "PARATI - CREDITO, FINANCIAMENTO E INVESTIMENTO S.A." }, - { - code: "328", - ispb: "05841967", - name: "COOPERATIVA DE ECONOMIA E CRÉDITO MÚTUO DOS FABRICANTES DE CALÇADOS DE SAPIRANGA", + code: "326", + ispb: "03311443", + name: "MEUTUDO S.A - SOCIEDADE DE CRÉDITO, FINANCIAMENTO E INVESTIMENTO", }, { code: "329", ispb: "32402502", name: "QI Sociedade de Crédito Direto S.A." }, { code: "330", ispb: "00556603", name: "BANCO BARI DE INVESTIMENTOS E FINANCIAMENTOS S.A." }, { code: "331", ispb: "13673855", - name: "Fram Capital Distribuidora de Títulos e Valores Mobiliários S.A.", + name: "OSLO CAPITAL DISTRIBUIDORA DE TÍTULOS E VALORES MOBILIÁRIOS S.A", }, { code: "332", @@ -361,24 +338,26 @@ export const BANKS: Bank[] = [ { code: "334", ispb: "15124464", name: "BANCO BESA S.A." }, { code: "335", ispb: "27098060", name: "Banco Digio S.A." }, { code: "336", ispb: "31872495", name: "Banco C6 S.A." }, - { code: "340", ispb: "09554480", name: "SUPERDIGITAL INSTITUIÇÃO DE PAGAMENTO S.A." }, { code: "341", ispb: "60701190", name: "ITAÚ UNIBANCO S.A." }, { code: "342", ispb: "32997490", name: "Creditas Sociedade de Crédito Direto S.A." }, + { code: "348", ispb: "33264668", name: "Banco XP S.A." }, { - code: "343", - ispb: "24537861", - name: "FFA SOCIEDADE DE CRÉDITO AO MICROEMPREENDEDOR E À EMPRESA DE PEQUENO PORTE LTDA.", + code: "349", + ispb: "27214112", + name: "AL5 S.A. SOCIEDADE DE CRÉDITO, FINANCIAMENTO E INVESTIMENTO", }, - { code: "348", ispb: "33264668", name: "Banco XP S.A." }, - { code: "349", ispb: "27214112", name: "AL5 S.A. CRÉDITO, FINANCIAMENTO E INVESTIMENTO" }, + { code: "350", ispb: "01330387", name: "COOPERATIVA DE CRÉDITO POPULAR DO BRASIL - CREHNOR" }, { - code: "350", - ispb: "01330387", - name: "COOPERATIVA DE CRÉDITO RURAL DE PEQUENOS AGRICULTORES E DA REFORMA AGRÁRIA DO CE", + code: "352", + ispb: "29162769", + name: "SANTANDER CORRETORA DE TÍTULOS E VALORES MOBILIÁRIOS S.A.", }, - { code: "352", ispb: "29162769", name: "TORO CORRETORA DE TÍTULOS E VALORES MOBILIÁRIOS S.A." }, { code: "355", ispb: "34335592", name: "ÓTIMO SOCIEDADE DE CRÉDITO DIRETO S.A." }, - { code: "358", ispb: "09464032", name: "MIDWAY S.A. - CRÉDITO, FINANCIAMENTO E INVESTIMENTO" }, + { + code: "358", + ispb: "09464032", + name: "MIDWAY S.A. - SOCIEDADE DE CRÉDITO, FINANCIAMENTO E INVESTIMENTO", + }, { code: "359", ispb: "05351887", name: "ZEMA CRÉDITO, FINANCIAMENTO E INVESTIMENTO S/A" }, { code: "360", @@ -386,11 +365,7 @@ export const BANKS: Bank[] = [ name: "TRINUS CAPITAL DISTRIBUIDORA DE TÍTULOS E VALORES MOBILIÁRIOS S.A.", }, { code: "362", ispb: "01027058", name: "CIELO S.A. - INSTITUIÇÃO DE PAGAMENTO" }, - { - code: "363", - ispb: "62285390", - name: "SINGULARE CORRETORA DE TÍTULOS E VALORES MOBILIÁRIOS S.A.", - }, + { code: "363", ispb: "62285390", name: "QI CORRETORA DE TÍTULOS E VALORES MOBILIÁRIOS S.A." }, { code: "364", ispb: "09089356", name: "EFÍ S.A. - INSTITUIÇÃO DE PAGAMENTO" }, { code: "365", @@ -398,41 +373,39 @@ export const BANKS: Bank[] = [ name: "SIMPAUL CORRETORA DE CAMBIO E VALORES MOBILIARIOS S.A.", }, { code: "366", ispb: "61533584", name: "BANCO SOCIETE GENERALE BRASIL S.A." }, - { - code: "367", - ispb: "34711571", - name: "VITREO DISTRIBUIDORA DE TÍTULOS E VALORES MOBILIÁRIOS S.A.", - }, { code: "368", ispb: "08357240", name: "Banco CSF S.A." }, { code: "370", ispb: "61088183", name: "Banco Mizuho do Brasil S.A." }, - { code: "371", ispb: "92875780", name: "WARREN CORRETORA DE VALORES MOBILIÁRIOS E CÂMBIO LTDA." }, { code: "373", ispb: "35977097", name: "UP.P SOCIEDADE DE EMPRÉSTIMO ENTRE PESSOAS S.A." }, - { code: "374", ispb: "27351731", name: "REALIZE CRÉDITO, FINANCIAMENTO E INVESTIMENTO S.A." }, + { + code: "374", + ispb: "27351731", + name: "REALIZE SOCIEDADE DE CRÉDITO, FINANCIAMENTO E INVESTIMENTO S.A.", + }, { code: "376", ispb: "33172537", name: "BANCO J.P. MORGAN S.A." }, { code: "377", ispb: "17826860", name: "BMS SOCIEDADE DE CRÉDITO DIRETO S.A." }, { code: "378", ispb: "01852137", name: "BANCO BRASILEIRO DE CRÉDITO SOCIEDADE ANÔNIMA" }, { code: "379", ispb: "01658426", - name: "COOPERFORTE - COOPERATIVA DE ECONOMIA E CRÉDITO MÚTUO DE FUNCIONÁRIOS DE INSTITU", + name: "COOPERFORTE COOPERATIVA DE CRÉDITO E INVESTIMENTOS LTDA", }, { code: "380", ispb: "22896431", name: "PICPAY INSTITUIçãO DE PAGAMENTO S.A." }, { code: "381", ispb: "60814191", name: "BANCO MERCEDES-BENZ DO BRASIL S.A." }, { code: "382", ispb: "04307598", - name: "FIDÚCIA SOCIEDADE DE CRÉDITO AO MICROEMPREENDEDOR E À EMPRESA DE PEQUENO PORTE L", + name: "FIDÚCIA SOCIEDADE DE CRÉDITO AO MICROEMPREENDEDOR E À EMPRESA DE PEQUENO PORTE LIMITADA.", }, { code: "383", ispb: "21018182", name: "EBANX INSTITUICAO DE PAGAMENTOS LTDA." }, { code: "384", ispb: "11165756", - name: "GLOBAL FINANÇAS SOCIEDADE DE CRÉDITO AO MICROEMPREENDEDOR E À EMPRESA DE PEQUENO", + name: "GLOBAL FINANÇAS SOCIEDADE DE CRÉDITO AO MICROEMPREENDEDOR E À EMPRESA DE PEQUENO PORTE LTDA.", }, { code: "385", ispb: "03844699", - name: "COOPERATIVA DE ECONOMIA E CREDITO MUTUO DOS TRABALHADORES PORTUARIOS DA GRANDE V", + name: "COOPERATIVA DE ECONOMIA E CREDITO MUTUO DOS TRABALHADORES PORTUARIOS DA GRANDE VITORIA - CREDESTIVA.", }, { code: "386", @@ -450,14 +423,14 @@ export const BANKS: Bank[] = [ ispb: "08673569", name: "F.D'GOLD - DISTRIBUIDORA DE TÍTULOS E VALORES MOBILIÁRIOS LTDA.", }, - { code: "396", ispb: "13884775", name: "HUB INSTITUIÇÃO DE PAGAMENTO S.A." }, + { code: "396", ispb: "13884775", name: "MAGALUPAY INSTITUIÇÃO DE PAGAMENTO S.A." }, { code: "397", ispb: "34088029", name: "LISTO SOCIEDADE DE CREDITO DIRETO S.A." }, { code: "398", ispb: "31749596", name: "IDEAL CORRETORA DE TÍTULOS E VALORES MOBILIÁRIOS S.A." }, { code: "399", ispb: "01701201", name: "Kirton Bank S.A. - Banco Múltiplo" }, { code: "400", ispb: "05491616", - name: "COOPERATIVA DE CRÉDITO, POUPANÇA E SERVIÇOS FINANCEIROS DO CENTRO OESTE - CREDIT", + name: "COOPERATIVA DE CRÉDITO, POUPANÇA E SERVIÇOS FINANCEIROS - EM LIQUIDAÇÃO EXTRAJUDICIAL", }, { code: "401", ispb: "15111975", name: "IUGU INSTITUIÇÃO DE PAGAMENTO S.A." }, { @@ -465,13 +438,21 @@ export const BANKS: Bank[] = [ ispb: "36947229", name: "COBUCCIO S/A - SOCIEDADE DE CRÉDITO, FINANCIAMENTO E INVESTIMENTOS", }, - { code: "403", ispb: "37880206", name: "CORA SOCIEDADE DE CRÉDITO DIRETO S.A." }, - { code: "404", ispb: "37241230", name: "SUMUP SOCIEDADE DE CRÉDITO DIRETO S.A." }, + { + code: "403", + ispb: "37880206", + name: "CORA SOCIEDADE DE CRÉDITO, FINANCIAMENTO E INVESTIMENTO S.A.", + }, + { + code: "404", + ispb: "37241230", + name: "SUMUP SOCIEDADE DE CRÉDITO, FINANCIAMENTO E INVESTIMENTO S.A.", + }, { code: "406", ispb: "37715993", name: "ACCREDITO - SOCIEDADE DE CRÉDITO DIRETO S.A." }, { code: "407", ispb: "00329598", - name: "ÍNDIGO INVESTIMENTOS DISTRIBUIDORA DE TÍTULOS E VALORES MOBILIÁRIOS LTDA.", + name: "SEFER INVESTIMENTOS DISTRIBUIDORA DE TÍTULOS E VALORES MOBILIÁRIOS LTDA - EM LIQUIDAÇÃO EXTRAJUDICIAL", }, { code: "408", ispb: "36586946", name: "BONUSPAGO SOCIEDADE DE CRÉDITO DIRETO S.A." }, { code: "410", ispb: "05684234", name: "PLANNER SOCIEDADE DE CRÉDITO DIRETO S.A." }, @@ -483,6 +464,7 @@ export const BANKS: Bank[] = [ { code: "412", ispb: "15173776", name: "SOCIAL BANK BANCO MÚLTIPLO S/A" }, { code: "413", ispb: "01858774", name: "BANCO BV S.A." }, { code: "414", ispb: "37526080", name: "LEND SOCIEDADE DE CRÉDITO DIRETO S.A." }, + { code: "415", ispb: "17157777", name: "BANCO NACIONAL S.A." }, { code: "416", ispb: "19324634", name: "LAMARA SOCIEDADE DE CRÉDITO DIRETO S.A." }, { code: "418", @@ -501,41 +483,39 @@ export const BANKS: Bank[] = [ { code: "426", ispb: "11285104", - name: "NEON FINANCEIRA - CRÉDITO, FINANCIAMENTO E INVESTIMENTO S.A.", + name: "NEON FINANCEIRA - SOCIEDADE DE CRÉDITO, FINANCIAMENTO E INVESTIMENTO S.A", }, { code: "427", ispb: "27302181", - name: "COOPERATIVA DE CREDITO DOS SERVIDORES DA UNIVERSIDADE FEDERAL DO ESPIRITO SANTO", + name: "COOPERATIVA DE CRÉDITO DOS SERVIDORES DA UNIVERSIDADE FEDERAL DO ESPIRITO SANTO", }, { code: "428", ispb: "39664698", name: "CREDSYSTEM SOCIEDADE DE CRÉDITO DIRETO S.A." }, - { code: "429", ispb: "05676026", name: "Crediare S.A. - Crédito, financiamento e investimento" }, { code: "430", ispb: "00204963", name: "COOPERATIVA DE CREDITO RURAL SEARA - CREDISEARA" }, { code: "433", ispb: "44077014", name: "BR-CAPITAL DISTRIBUIDORA DE TÍTULOS E VALORES MOBILIÁRIOS S.A.", }, - { code: "435", ispb: "38224857", name: "DELCRED SOCIEDADE DE CRÉDITO DIRETO S.A." }, + { code: "435", ispb: "38224857", name: "DELFINANCE SOCIEDADE DE CREDITO DIRETO S.A." }, { code: "438", ispb: "67030395", - name: "TRUSTEE DISTRIBUIDORA DE TÍTULOS E VALORES MOBILIÁRIOS LTDA.", + name: "TRUSTEE DISTRIBUIDORA DE TÍTULOS E VALORES MOBILIÁRIOS LTDA. - EM LIQUIDAÇÃO EXTRAJUDICIAL", }, { code: "439", ispb: "16695922", name: "ID CORRETORA DE TÍTULOS E VALORES MOBILIÁRIOS S.A." }, - { code: "440", ispb: "82096447", name: "CREDIBRF - COOPERATIVA DE CRÉDITO" }, + { code: "440", ispb: "82096447", name: "CREDI&GENTE - COOPERATIVA DE CRÉDITO E INVESTIMENTOS" }, { - code: "442", - ispb: "87963450", - name: "MAGNETIS - DISTRIBUIDORA DE TÍTULOS E VALORES MOBILIÁRIOS LTDA", + code: "443", + ispb: "39416705", + name: "OCTA SOCIEDADE DE CRÉDITO DIRETO S.A. - EM LIQUIDAÇÃO EXTRAJUDICIAL", }, - { code: "443", ispb: "39416705", name: "CREDIHOME SOCIEDADE DE CRÉDITO DIRETO S.A." }, { code: "444", ispb: "40654622", name: "TRINUS SOCIEDADE DE CRÉDITO DIRETO S.A." }, { code: "445", ispb: "35551187", name: "PLANTAE S.A. - CRÉDITO, FINANCIAMENTO E INVESTIMENTO" }, { code: "447", ispb: "12392983", - name: "MIRAE ASSET WEALTH MANAGEMENT (BRAZIL) CORRETORA DE CÂMBIO, TÍTULOS E VALORES MO", + name: "MIRAE ASSET (BRASIL) CORRETORA DE CÂMBIO, TÍTULOS E VALORES MOBILIÁRIOS LTDA.", }, { code: "448", @@ -543,7 +523,7 @@ export const BANKS: Bank[] = [ name: "HEMERA DISTRIBUIDORA DE TÍTULOS E VALORES MOBILIÁRIOS LTDA.", }, { code: "449", ispb: "37555231", name: "DM SOCIEDADE DE CRÉDITO DIRETO S.A." }, - { code: "450", ispb: "13203354", name: "FITBANK INSTITUIÇÃO DE PAGAMENTOS ELETRÔNICOS S.A." }, + { code: "450", ispb: "13203354", name: "FITS INSTITUIÇÃO DE PAGAMENTO S.A." }, { code: "451", ispb: "40475846", name: "J17 - SOCIEDADE DE CRÉDITO DIRETO S/A" }, { code: "452", ispb: "39676772", name: "CREDIFIT SOCIEDADE DE CRÉDITO DIRETO S.A." }, { @@ -554,7 +534,7 @@ export const BANKS: Bank[] = [ { code: "455", ispb: "38429045", - name: "FÊNIX DISTRIBUIDORA DE TÍTULOS E VALORES MOBILIÁRIOS LTDA.", + name: "VIS DISTRIBUIDORA DE TÍTULOS E VALORES MOBILIÁRIOS LTDA", }, { code: "456", ispb: "60498557", name: "Banco MUFG Brasil S.A." }, { code: "457", ispb: "39587424", name: "UY3 SOCIEDADE DE CRÉDITO DIRETO S/A" }, @@ -563,11 +543,6 @@ export const BANKS: Bank[] = [ ispb: "07253654", name: "HEDGE INVESTMENTS DISTRIBUIDORA DE TÍTULOS E VALORES MOBILIÁRIOS LTDA.", }, - { - code: "459", - ispb: "04546162", - name: "COOPERATIVA DE CRÉDITO MÚTUO DE SERVIDORES PÚBLICOS DO ESTADO DE SÃO PAULO - CRE", - }, { code: "460", ispb: "42047025", name: "UNAVANTI SOCIEDADE DE CRÉDITO DIRETO S/A" }, { code: "461", ispb: "19540550", name: "ASAAS GESTÃO FINANCEIRA INSTITUIÇÃO DE PAGAMENTO S.A." }, { code: "462", ispb: "39908427", name: "STARK SOCIEDADE DE CRÉDITO DIRETO S.A." }, @@ -581,22 +556,18 @@ export const BANKS: Bank[] = [ { code: "467", ispb: "33886862", - name: "MASTER S/A CORRETORA DE CâMBIO, TíTULOS E VALORES MOBILIáRIOS", + name: "MASTER S/A CORRETORA DE CÂMBIO, TÍTULOS E VALORES MOBILIÁRIOS - EM LIQUIDAÇÃO EXTRAJUDICIAL", }, { code: "468", ispb: "04862600", name: "PORTOSEG S.A. - CREDITO, FINANCIAMENTO E INVESTIMENTO" }, { code: "469", ispb: "07138049", - name: "LIGA INVEST DISTRIBUIDORA DE TÍTULOS E VALORES MOBILIÁRIOS LTDA", + name: "PICPAY INVEST DISTRIBUIDORA DE TÍTULOS E VALORES MOBILIÁRIOS LTDA", }, { code: "470", ispb: "18394228", name: "CDC SOCIEDADE DE CRÉDITO DIRETO S.A." }, - { - code: "471", - ispb: "04831810", - name: "COOPERATIVA DE ECONOMIA E CREDITO MUTUO DOS SERVIDORES PUBLICOS DE PINHÃO - CRES", - }, { code: "473", ispb: "33466988", name: "Banco Caixa Geral - Brasil S.A." }, { code: "475", ispb: "10371492", name: "Banco Yamaha Motor do Brasil S.A." }, + { code: "476", ispb: "45860531", name: "IDEA MAKER INSTITUICAO DE PAGAMENTO LTDA" }, { code: "477", ispb: "33042953", name: "Citibank N.A." }, { code: "478", @@ -605,27 +576,24 @@ export const BANKS: Bank[] = [ }, { code: "479", ispb: "60394079", name: "Banco ItauBank S.A." }, { code: "481", ispb: "43599047", name: "SUPERLÓGICA SOCIEDADE DE CRÉDITO DIRETO S.A." }, - { code: "482", ispb: "42259084", name: "SBCASH SOCIEDADE DE CRÉDITO DIRETO S.A." }, + { code: "482", ispb: "42259084", name: "ARTTA SOCIEDADE DE CRÉDITO DIRETO S.A" }, { code: "484", ispb: "36864992", - name: "MAF DISTRIBUIDORA DE TÍTULOS E VALORES MOBILIÁRIOS S.A.", + name: "APEX DISTRIBUIDORA DE TÍTULOS E VALORES MOBILIÁRIOS S.A.", }, { code: "487", ispb: "62331228", name: "DEUTSCHE BANK S.A. - BANCO ALEMAO" }, { code: "488", ispb: "46518205", name: "JPMorgan Chase Bank, National Association" }, { code: "495", ispb: "44189447", name: "Banco de La Provincia de Buenos Aires" }, - { code: "505", ispb: "32062580", name: "Banco Credit Suisse (Brasil) S.A." }, + { code: "496", ispb: "45283173", name: "BBVA BRASIL BANCO DE INVESTIMENTO S.A." }, + { code: "505", ispb: "32062580", name: "BANCO UBS (BRASIL) S.A." }, { code: "506", ispb: "42066258", name: "RJI CORRETORA DE TITULOS E VALORES MOBILIARIOS LTDA" }, { code: "507", ispb: "37229413", name: "SOCIEDADE DE CRÉDITO, FINANCIAMENTO E INVESTIMENTO EFÍ S.A.", }, - { - code: "508", - ispb: "61384004", - name: "AVENUE SECURITIES DISTRIBUIDORA DE TÍTULOS E VALORES MOBILIÁRIOS LTDA.", - }, + { code: "508", ispb: "61384004", name: "AVENUE SECURITIES BANCO DE INVESTIMENTO S.A." }, { code: "509", ispb: "13935893", name: "CELCOIN INSTITUICAO DE PAGAMENTO S.A." }, { code: "510", ispb: "39738065", name: "FFCRED SOCIEDADE DE CRÉDITO DIRETO S.A.." }, { code: "511", ispb: "44683140", name: "MAGNUM SOCIEDADE DE CRÉDITO DIRETO S.A." }, @@ -634,8 +602,10 @@ export const BANKS: Bank[] = [ ispb: "36266751", name: "FINVEST DISTRIBUIDORA DE TÍTULOS E VALORES MOBILIÁRIOS LTDA.", }, - { code: "513", ispb: "44728700", name: "ATF CREDIT SOCIEDADE DE CRÉDITO DIRETO S.A." }, + { code: "513", ispb: "44728700", name: "ATF SOCIEDADE DE CRÉDITO DIRETO S.A." }, + { code: "514", ispb: "73302408", name: "EXIM SOCIEDADE CORRETORA DE CÂMBIO LTDA" }, { code: "516", ispb: "36583700", name: "QISTA S.A. - CRÉDITO, FINANCIAMENTO E INVESTIMENTO" }, + { code: "517", ispb: "03816413", name: "PAGUEVELOZ INSTITUIÇÃO DE PAGAMENTO LTDA." }, { code: "518", ispb: "37679449", @@ -646,6 +616,7 @@ export const BANKS: Bank[] = [ ispb: "40768766", name: "LIONS TRUST DISTRIBUIDORA DE TÍTULOS E VALORES MOBILIÁRIOS LTDA.", }, + { code: "520", ispb: "44705774", name: "SOMAPAY SOCIEDADE DE CRÉDITO DIRETO S.A." }, { code: "521", ispb: "44019481", name: "PEAK SOCIEDADE DE EMPRÉSTIMO ENTRE PESSOAS S.A." }, { code: "522", ispb: "47593544", name: "RED SOCIEDADE DE CRÉDITO DIRETO S.A." }, { code: "523", ispb: "44292580", name: "HR DIGITAL - SOCIEDADE DE CRÉDITO DIRETO S/A" }, @@ -660,51 +631,247 @@ export const BANKS: Bank[] = [ { code: "528", ispb: "34829992", - name: "REAG DISTRIBUIDORA DE TÍTULOS E VALORES MOBILIÁRIOS S.A.", + name: "CBSF DISTRIBUIDORA DE TÍTULOS E VALORES MOBILIÁRIOS S.A.- EM LIQUIDAÇÃO EXTRAJUDICIAL", }, { code: "529", ispb: "17079937", name: "PINBANK BRASIL INSTITUIÇÃO DE PAGAMENTO S.A." }, { code: "530", ispb: "47873449", name: "SER FINANCE SOCIEDADE DE CRÉDITO DIRETO S.A." }, - { code: "532", ispb: "45745537", name: "EAGLE SOCIEDADE DE CRÉDITO DIRETO S.A." }, + { code: "531", ispb: "34337707", name: "BMP SOCIEDADE DE CRÉDITO DIRETO S.A" }, + { code: "532", ispb: "45745537", name: "FUTURO SOCIEDADE DE CRÉDITO DIRETO S.A." }, + { code: "533", ispb: "22575466", name: "SRM BANK INSTITUIÇÃO DE PAGAMENTO S/A" }, { code: "534", ispb: "00714671", name: "EWALLY INSTITUIÇÃO DE PAGAMENTO S.A." }, - { code: "535", ispb: "39519944", name: "MARÚ SOCIEDADE DE CRÉDITO DIRETO S.A." }, + { code: "535", ispb: "39519944", name: "OPEA SOCIEDADE DE CRÉDITO DIRETO S.A." }, { code: "536", ispb: "20855875", name: "NEON PAGAMENTOS S.A. - INSTITUIÇÃO DE PAGAMENTO" }, { code: "537", ispb: "45756448", - name: "MICROCASH SOCIEDADE DE CRÉDITO AO MICROEMPREENDEDOR E À EMPRESA DE PEQUENO PORTE", + name: "SELECT CREDIT SOCIEDADE DE CRÉDITO AO MICROEMPREENDEDOR E À EMPRESA DE PEQUENO PORTE LTDA.", }, + { code: "538", ispb: "20251847", name: "SUDACRED SOCIEDADE DE CRÉDITO DIRETO S.A." }, { code: "539", ispb: "00122327", name: "SANTINVEST S.A. - CREDITO, FINANCIAMENTO E INVESTIMENTOS", }, + { code: "540", ispb: "04849745", name: "HBI SOCIEDADE DE CRÉDITO DIRETO S/A." }, { code: "541", ispb: "00954288", name: "FUNDO GARANTIDOR DE CREDITOS - FGC" }, - { code: "545", ispb: "17352220", name: "SENSO CORRETORA DE CAMBIO E VALORES MOBILIARIOS S.A" }, - { code: "546", ispb: "30980539", name: "U4C INSTITUIÇÃO DE PAGAMENTO S.A." }, + { code: "542", ispb: "18189547", name: "CLOUDWALK INSTITUIÇÃO DE PAGAMENTO E SERVICOS LTDA" }, + { + code: "543", + ispb: "92825397", + name: "COOPERATIVA DE ECONOMIA E CRÉDITO MÚTUO DOS ELETRICITÁRIOS E DOS TRABALHADORES DAS EMPRESAS DO SETOR DE ENERGIA - COOPCRECE", + }, + { code: "544", ispb: "38593706", name: "MULTICRED SOCIEDADE DE CRÉDITO DIRETO S.A." }, + { code: "546", ispb: "30980539", name: "OKTO INSTITUIÇÃO DE PAGAMENTO S.A." }, + { code: "547", ispb: "45331622", name: "BNK DIGITAL SOCIEDADE DE CRÉDITO DIRETO S.A." }, + { + code: "548", + ispb: "06249129", + name: "RPW S/A SOCIEDADE DE CRÉDITO, FINANCIAMENTO E INVESTIMENTO", + }, + { code: "550", ispb: "32074986", name: "BEETELLER INSTITUIÇÃO DE PAGAMENTO LTDA." }, + { + code: "551", + ispb: "48967968", + name: "VERT DISTRIBUIDORA DE TÍTULOS E VALORES MOBILIÁRIOS LTDA", + }, + { code: "552", ispb: "32192325", name: "UZZIPAY INSTITUIÇÃO DE PAGAMENTO S.A." }, + { code: "553", ispb: "48707451", name: "PERCAPITAL SOCIEDADE DE CRÉDITO DIRETO S.A." }, + { code: "554", ispb: "28811341", name: "BANCO STONEX S.A." }, + { + code: "555", + ispb: "02682287", + name: "PAN FINANCEIRA S.A. - SOCIEDADE DE CRÉDITO, FINANCIAMENTO E INVESTIMENTOS", + }, + { code: "556", ispb: "40333582", name: "SAYGO CORRETORA DE CÂMBIO S.A." }, + { code: "557", ispb: "30944783", name: "PAGPRIME INSTITUICAO DE PAGAMENTO LTDA" }, + { + code: "559", + ispb: "49288113", + name: "KANASTRA FINANCEIRA S.A, CREDITO, FINANCIAMENTO E INVESTIMENTO", + }, + { code: "560", ispb: "21995256", name: "MAG INSTITUICAO DE PAGAMENTO LTDA" }, + { code: "561", ispb: "20757199", name: "PAY4FUN INSTITUICAO DE PAGAMENTO S.A." }, + { + code: "562", + ispb: "18684408", + name: "AZIMUT BRASIL DISTRIBUIDORA DE TÍTULOS E VALORES MOBILIÁRIOS LTDA", + }, + { code: "563", ispb: "40276692", name: "PROTEGE CASH INSTITUIÇÃO DE PAGAMENTO S.A." }, + { + code: "564", + ispb: "63019146", + name: "ANKOR CAPITAL - SOCIEDADE DE CRÉDITO, FINANCIAMENTO E INVESTIMENTO S/A", + }, + { code: "565", ispb: "74014747", name: "ÁGORA CORRETORA DE TITULOS E VALORES MOBILIARIOS S.A." }, + { code: "566", ispb: "23114447", name: "FLAGSHIP INSTITUICAO DE PAGAMENTO LTDA" }, + { + code: "567", + ispb: "33040601", + name: "MERCANTIL FINANCEIRA S.A. - SOCIEDADE DE CRÉDITO, FINANCIAMENTO E INVESTIMENTO.", + }, + { code: "568", ispb: "49933388", name: "BRCONDOS SOCIEDADE DE CRÉDITO DIRETO S.A." }, + { code: "569", ispb: "12473687", name: "CONTA PRONTA INSTITUICAO DE PAGAMENTO LTDA" }, + { + code: "571", + ispb: "50489148", + name: "MONTE BRAVO CORRETORA DE TÍTULOS E VALORES MOBILIÁRIOS S.A.", + }, + { code: "572", ispb: "51414521", name: "ALL IN CRED SOCIEDADE DE CREDITO DIRETO S.A." }, + { code: "573", ispb: "18282093", name: "OXY COMPANHIA HIPOTECÁRIA" }, + { code: "574", ispb: "48756121", name: "A55 SOCIEDADE DE CRÉDITO DIRETO S.A." }, + { code: "575", ispb: "48584954", name: "DGBK CREDIT S.A. - SOCIEDADE DE CRÉDITO DIRETO." }, + { code: "576", ispb: "11351086", name: "MERCADO BITCOIN INSTITUICAO DE PAGAMENTO LTDA" }, + { + code: "577", + ispb: "10663610", + name: "DESENVOLVE SP - AGÊNCIA DE FOMENTO DO ESTADO DE SÃO PAULO S.A.", + }, + { code: "579", ispb: "49555647", name: "QUADRA SOCIEDADE DE CRÉDITO DIRETO S.A." }, + { code: "585", ispb: "50946592", name: "SETHI SOCIEDADE DE CRÉDITO DIRETO S.A." }, + { code: "586", ispb: "35810871", name: "Z1 INSTITUIÇÃO DE PAGAMENTO LTDA." }, + { + code: "587", + ispb: "37678915", + name: "FIDD DISTRIBUIDORA DE TÍTULOS E VALORES MOBILIÁRIOS LTDA.", + }, + { code: "588", ispb: "20308187", name: "AVANCARD PROVER INSTITUIÇÃO DE PAGAMENTO LTDA" }, + { code: "589", ispb: "51212088", name: "G5 SOCIEDADE DE CRÉDITO DIRETO S.A." }, + { + code: "590", + ispb: "40473435", + name: "REPASSES FINANCEIROS E SOLUCOES TECNOLOGICAS INSTITUICAO DE PAGAMENTO S.A.", + }, + { + code: "591", + ispb: "02671743", + name: "BANVOX DISTRIBUIDORA DE TÍTULOS E VALORES MOBILIÁRIOS LTDA - EM LIQUIDAÇÃO EXTRAJUDICIAL", + }, + { code: "592", ispb: "45548763", name: "INSTITUIÇÃO DE PAGAMENTOS MAPS LTDA." }, + { code: "593", ispb: "27084098", name: "TRANSFEERA INSTITUIÇÃO DE PAGAMENTO S.A" }, + { + code: "594", + ispb: "48703388", + name: "ASA SOCIEDADE DE CRÉDITO FINANCIAMENTO E INVESTIMENTO S.A.", + }, + { code: "595", ispb: "19468242", name: "IFOOD PAGO INSTITUIÇÃO DE PAGAMENTO S.A." }, + { code: "596", ispb: "39696395", name: "CACTVS INSTITUICAO DE PAGAMENTO S.A" }, + { code: "597", ispb: "34747388", name: "ISSUER INSTITUICAO DE PAGAMENTO LTDA." }, + { code: "598", ispb: "50626276", name: "KONECT SOCIEDADE DE CRÉDITO DIRETO S/A" }, + { + code: "599", + ispb: "36321990", + name: "AGORACRED S/A SOCIEDADE DE CRÉDITO, FINANCIAMENTO E INVESTIMENTO", + }, { code: "600", ispb: "59118133", name: "Banco Luso Brasileiro S.A." }, { code: "604", ispb: "31895683", name: "Banco Industrial do Brasil S.A." }, { code: "610", ispb: "78626983", name: "Banco VR S.A." }, { code: "611", ispb: "61820817", name: "Banco Paulista S.A." }, { code: "612", ispb: "31880826", name: "Banco Guanabara S.A." }, { code: "613", ispb: "60850229", name: "Omni Banco S.A." }, + { code: "614", ispb: "52440987", name: "SANTS SOCIEDADE DE CRÉDITO DIRETO S.A." }, + { code: "615", ispb: "37470405", name: "SMART SOLUTIONS GROUP INSTITUICAO DE PAGAMENTO LTDA" }, + { code: "619", ispb: "49931906", name: "TRIO INSTITUICAO DE PAGAMENTO LTDA." }, + { code: "620", ispb: "51342763", name: "REVOLUT SOCIEDADE DE CRÉDITO DIRETO S.A." }, { code: "623", ispb: "59285411", name: "Banco Pan S.A." }, { code: "626", ispb: "61348538", name: "BANCO C6 CONSIGNADO S.A." }, - { code: "630", ispb: "58497702", name: "BANCO LETSBANK S.A." }, + { code: "630", ispb: "58497702", name: "BANCO LETSBANK S.A. - EM LIQUIDAÇÃO EXTRAJUDICIAL" }, + { code: "632", ispb: "52586293", name: "Z-ON SOCIEDADE DE CRÉDITO DIRETO S.A." }, { code: "633", ispb: "68900810", name: "Banco Rendimento S.A." }, { code: "634", ispb: "17351180", name: "BANCO TRIANGULO S.A." }, + { code: "636", ispb: "40112555", name: "GIRO - SOCIEDADE DE CRÉDITO DIRETO S/A" }, { code: "637", ispb: "60889128", name: "BANCO SOFISA S.A." }, { code: "643", ispb: "62144175", name: "Banco Pine S.A." }, - { code: "653", ispb: "61024352", name: "BANCO VOITER S.A." }, + { code: "644", ispb: "54647259", name: "321 SOCIEDADE DE CRÉDITO DIRETO S.A." }, + { + code: "646", + ispb: "91669747", + name: "DM SOCIEDADE DE CRÉDITO, FINANCIAMENTO E INVESTIMENTO S.A", + }, + { code: "651", ispb: "25104230", name: "PAGARE INSTITUICAO DE PAGAMENTO S.A." }, + { code: "653", ispb: "61024352", name: "BANCO PLENO S.A. - EM LIQUIDAÇÃO EXTRAJUDICIAL" }, { code: "654", ispb: "92874270", name: "BANCO DIGIMAIS S.A." }, { code: "655", ispb: "59588111", name: "Banco Votorantim S.A." }, + { code: "659", ispb: "35210410", name: "ONEKEY PAYMENTS INSTITUICAO DE PAGAMENTO SA" }, + { code: "660", ispb: "34471744", name: "PAGME INSTITUIÇÃO DE PAGAMENTO LTDA." }, + { code: "661", ispb: "55428859", name: "FREEX SOCIEDADE CORRETORA DE CÂMBIO S.A." }, + { code: "662", ispb: "32708748", name: "WE PAY OUT INSTITUICAO DE PAGAMENTO LTDA." }, + { + code: "663", + ispb: "44782130", + name: "ACTUAL DISTRIBUIDORA DE TÍTULOS E VALORES MOBILIÁRIOS S.A.", + }, + { code: "665", ispb: "20018183", name: "STARK BANK S.A. - INSTITUICAO DE PAGAMENTO" }, + { code: "668", ispb: "48632754", name: "CELCOIN SOCIEDADE DE CRÉDITO DIRETO S.A." }, + { code: "669", ispb: "47133056", name: "TRANSFERO INSTITUICAO DE PAGAMENTO LTDA." }, + { code: "670", ispb: "11491029", name: "BSN PAGAMENTOS INSTITUIÇÃO DE PAGAMENTO LTDA" }, + { code: "671", ispb: "26264220", name: "ZERO INSTITUIÇÃO DE PAGAMENTO S.A." }, + { + code: "672", + ispb: "53505601", + name: "STONE SOCIEDADE DE CREDITO, FINANCIAMENTO E INVESTIMENTO S.A.", + }, + { + code: "673", + ispb: "08482873", + name: "COOPERATIVA DE CRÉDITO RURAL DO AGRESTE ALAGOANO - COOPERAGRE", + }, + { code: "674", ispb: "27970567", name: "HINOVA PAY INSTITUICAO DE PAGAMENTO S.A." }, + { code: "675", ispb: "30723871", name: "CASAS BAHIA PAY INSTITUIÇÃO DE PAGAMENTO LTDA." }, + { + code: "676", + ispb: "35479592", + name: "DUFRIO FINANCEIRA, CRÉDITO, FINANCIAMENTO E INVESTIMENTOS S.A.", + }, + { code: "677", ispb: "33630661", name: "GOWD INSTITUIÇÃO DE PAGAMENTO LTDA." }, + { code: "678", ispb: "45716916", name: "FIDEM SOCIEDADE DE CRÉDITO DIRETO S/A" }, + { code: "679", ispb: "36690516", name: "PAY INSTITUICAO DE PAGAMENTO S.A." }, + { code: "680", ispb: "55823094", name: "DELTA GLOBAL SOCIEDADE DE CRÉDITO DIRETO S.A." }, + { code: "681", ispb: "50871921", name: "MT INSTITUICAO DE PAGAMENTO SA" }, + { code: "682", ispb: "46505612", name: "MONERY INSTITUICAO DE PAGAMENTO S.A." }, + { code: "683", ispb: "30507541", name: "BRASIL CASH INSTITUICAO DE PAGAMENTO S.A" }, + { + code: "684", + ispb: "56392166", + name: "HARMOS S.A. - SOCIEDADE DE CRÉDITO, FINANCIAMENTO E INVESTIMENTO", + }, + { code: "685", ispb: "26615279", name: "TYCOON TECHNOLOGY INSTITUICAO DE PAGAMENTO S.A" }, + { code: "686", ispb: "12481100", name: "BIZ INSTITUIÇÃO DE PAGAMENTO S.A." }, + { code: "687", ispb: "35340796", name: "INCO SOCIEDADE DE EMPRÉSTIMO ENTRE PESSOAS S.A." }, + { code: "688", ispb: "43978697", name: "KIKAI SOCIEDADE DE CRÉDITO DIRETO S.A." }, + { code: "690", ispb: "16814330", name: "BK INSTITUIÇÃO DE PAGAMENTO S.A." }, + { code: "691", ispb: "49686505", name: "WASU - WALLET SUPPORT INSTITUIÇÃO DE PAGAMENTO LTDA" }, + { code: "692", ispb: "56198117", name: "SQUID SOCIEDADE DE CRÉDITO DIRETO S.A." }, + { code: "693", ispb: "32820711", name: "EFEX INSTITUIÇÃO DE PAGAMENTO S.A." }, + { code: "694", ispb: "54811417", name: "WOOVI INSTITUICAO DE PAGAMENTO LTDA" }, + { code: "695", ispb: "35523352", name: "BEES INSTITUICAO DE PAGAMENTO LTDA." }, + { code: "696", ispb: "50032584", name: "LOAN BRASIL SOCIEDADE DE CRÉDITO DIRETO S.A." }, + { code: "697", ispb: "45056494", name: "JM CORRETORA DE CÂMBIO LTDA." }, + { code: "698", ispb: "58367961", name: "BIT SOCIEDADE DE CRÉDITO DIRETO S.A." }, + { code: "699", ispb: "59396084", name: "BFC SOCIEDADE DE CRÉDITO DIRETO S.A." }, + { code: "700", ispb: "53822116", name: "MW INSTITUICAO DE PAGAMENTO LTDA" }, + { + code: "701", + ispb: "51118615", + name: "INTEGRAÇÃO DE CRÉDITO E COBRANÇA SOCIEDADE DE CRÉDITO DIRETO S.A.", + }, + { + code: "703", + ispb: "10440482", + name: "GETNET ADQUIRÊNCIA E SERVIÇOS PARA MEIOS DE PAGAMENTO S.A. INSTITUIÇÃO DE PAGAMENTO", + }, + { code: "704", ispb: "44663846", name: "FESTOR INSTITUIÇÃO DE PAGAMENTO LTDA." }, { code: "707", ispb: "62232889", name: "Banco Daycoval S.A." }, - { code: "712", ispb: "78632767", name: "Banco Ourinvest S.A." }, - { code: "720", ispb: "80271455", name: "BANCO RNX S.A." }, - { code: "739", ispb: "00558456", name: "Banco Cetelem S.A." }, + { code: "708", ispb: "33588252", name: "BANCO INDUSCRED DE INVESTIMENTO S.A." }, + { code: "712", ispb: "78632767", name: "OURIBANK S.A. BANCO MÚLTIPLO" }, + { code: "714", ispb: "00411939", name: "FINAMAX S.A. - CREDITO, FINANCIAMENTO E INVESTIMENTO" }, + { + code: "719", + ispb: "33884941", + name: "BANCO MASTER MÚLTIPLO S.A. - EM LIQUIDAÇÃO EXTRAJUDICIAL", + }, { code: "741", ispb: "00517645", name: "BANCO RIBEIRAO PRETO S.A." }, { code: "743", ispb: "00795423", name: "Banco Semear S.A." }, { code: "745", ispb: "33479023", name: "Banco Citibank S.A." }, - { code: "746", ispb: "30723886", name: "Banco Modal S.A." }, { code: "747", ispb: "01023570", name: "Banco Rabobank International Brasil S.A." }, { code: "748", ispb: "01181521", name: "BANCO COOPERATIVO SICREDI S.A." }, { code: "751", ispb: "29030467", name: "Scotiabank Brasil S.A. Banco Múltiplo" }, @@ -714,4 +881,72 @@ export const BANKS: Bank[] = [ { code: "755", ispb: "62073200", name: "Bank of America Merrill Lynch Banco Múltiplo S.A." }, { code: "756", ispb: "02038232", name: "BANCO COOPERATIVO SICOOB S.A. - BANCO SICOOB" }, { code: "757", ispb: "02318507", name: "BANCO KEB HANA DO BRASIL S.A." }, + { code: "759", ispb: "42963742", name: "BANSUR JM SOCIEDADE DE CRÉDITO DIRETO S/A" }, + { + code: "760", + ispb: "34139916", + name: "EMCASH SERVIÇOS FINANCEIROS SOCIEDADE DE EMPRÉSTIMO ENTRE PESSOAS S.A.", + }, + { + code: "761", + ispb: "53842122", + name: "URBANO S.A. - SOCIEDADE DE CRÉDITO, FINANCIAMENTO E INVESTIMENTO", + }, + { code: "763", ispb: "56106523", name: "VUE INSTITUIÇÃO DE PAGAMENTO S.A." }, + { + code: "764", + ispb: "04306351", + name: "INDEPENDÊNCIA COOPERATIVA DE CRÉDITO E INVESTIMENTO - INDEPENDÊNCIA COOPERATIVA DE CRÉDITO", + }, + { code: "765", ispb: "37753531", name: "PAGSMILE INSTITUIÇÃO DE PAGAMENTO LTDA." }, + { code: "766", ispb: "52833288", name: "LB PAY INSTITUIÇÃO DE PAGAMENTO LTDA" }, + { + code: "767", + ispb: "62264924", + name: "QORE DISTRIBUIDORA DE TITULOS E VALORES MOBILIÁRIOS LTDA", + }, + { + code: "768", + ispb: "20443996", + name: "BECKER FINANCEIRA S.A. - CRÉDITO, FINANCIAMENTO E INVESTIMENTO", + }, + { code: "769", ispb: "24313102", name: "99PAY INSTITUICAO DE PAGAMENTO S.A." }, + { code: "770", ispb: "38320462", name: "V3 INSTITUICAO DE PAGAMENTO S.A." }, + { code: "771", ispb: "57824223", name: "WX INSTITUICAO DE PAGAMENTO LTDA" }, + { + code: "772", + ispb: "20833992", + name: "COOPERATIVA DE CRÉDITO MÚTUO DOS EMPREGADOS DO CENTRO UNIVERSITÁRIO NEWTON PAIVA LTDA. - CREDIPAIVA", + }, + { code: "773", ispb: "53908413", name: "KIWIFY INSTITUICAO DE PAGAMENTO LTDA" }, + { code: "774", ispb: "33959738", name: "MOVA SOCIEDADE DE EMPRÉSTIMO ENTRE PESSOAS S.A." }, + { code: "775", ispb: "47381104", name: "CONTAAZUL INSTITUICAO DE PAGAMENTO LTDA." }, + { code: "778", ispb: "58674454", name: "PB SOCIEDADE DE CREDITO DIRETO S.A." }, + { code: "780", ispb: "12102128", name: "SAFETYPAY BRASIL INSTITUICAO DE PAGAMENTO LTDA" }, + { + code: "781", + ispb: "58006471", + name: "BARU DISTRIBUIDORA DE TÍTULOS E VALORES MOBILIÁRIOS LTDA.", + }, + { code: "783", ispb: "31680151", name: "SWAP INSTITUIÇÃO DE PAGAMENTO S.A." }, + { code: "785", ispb: "53058329", name: "LA FINTECA INSTITUICAO DE PAGAMENTO LTDA" }, + { code: "786", ispb: "52076716", name: "AWX BRASIL INSTITUICAO DE PAGAMENTO LTDA" }, + { code: "787", ispb: "10789035", name: "ATTRUS INSTITUIÇÃO DE PAGAMENTO S/A" }, + { code: "788", ispb: "35713491", name: "PROTOTYPE INSTITUICAO DE PAGAMENTO S.A." }, + { code: "789", ispb: "57654096", name: "APUSDIGITAL INSTITUICAO DE PAGAMENTO LTDA" }, + { code: "790", ispb: "54024532", name: "MAX INSTITUIÇÃO DE PAGAMENTO LTDA" }, + { + code: "791", + ispb: "62448932", + name: "MULTIPLIKE FINANCEIRA S.A. SOCIEDADE DE CRÉDITO, FINANCIAMENTO E INVESTIMENTO", + }, + { code: "792", ispb: "44921281", name: "NIXFIN SOCIEDADE DE CRÉDITO DIRETO S.A." }, + { + code: "793", + ispb: "61021673", + name: "MAGALUPAY - SOCIEDADE DE CRÉDITO, FINANCIAMENTO E INVESTIMENTO S.A.", + }, + { code: "794", ispb: "44323831", name: "ATUAL SOCIEDADE CORRETORA DE CÂMBIO LTDA" }, + { code: "795", ispb: "52067630", name: "BANCO TRATON BRASIL S.A." }, + { code: "804", ispb: "32343119", name: "MÊNTORE INSTITUIÇÃO DE PAGAMENTO S.A." }, ]; diff --git a/src/is-valid-bank-account/constants.ts b/src/is-valid-bank-account/constants.ts index c5afee4d..00a566a3 100644 --- a/src/is-valid-bank-account/constants.ts +++ b/src/is-valid-bank-account/constants.ts @@ -11,18 +11,22 @@ */ export const COMPE_CODES = - "001003004007010011012014015016017018021024025029033036037040041047060062063064065066069070" + - "074075076077078079080081082083084085088089093094095096097098099100101102104105107111113114" + - "117119120121122124125126127128129130131132133134136138139140141142143144145146149157159173" + - "174177180183184188189190191194195196197208212213217218222224233237241243246249250253254259" + - "260265266268269270271272273274276278279280281283285288289290292293296298299300301306307309" + - "310311312313318319320321322323324325326328329330331332334335336340341342343348349350352355" + - "358359360362363364365366367368370371373374376377378379380381382383384385386387389390391393" + - "394395396397398399400401402403404406407408410411412413414416418419421422423425426427428429" + - "430433435438439440442443444445447448449450451452454455456457458459460461462463464465467468" + - "469470471473475477478479481482484487488495505506507508509510511512513516518519521522523524" + - "525526527528529530532534535536537539541545546600604610611612613623626630633634637643653654" + - "655707712720739741743745746747748751752753754755756757"; + "001003004007010011012014015016017018021023024025033036037040041047060063064065066069070074" + + "075076077078079080081082083084085088089093094095096097099100101102104105107111113119120121" + + "122124125126127128129130131132133134136138139140141142143144145146149157159173174180183188" + + "189190191194195196197208212213217218222224233237241243246249250254259260265266268269271272" + + "273274276278280281283288289290292293296298299300301305307310312318319320321322323324326329" + + "330331332334335336341342348349350352355358359360362363364365366368370373374376377378379380" + + "381382383384385386387389390391393394395396397398399400401402403404406407408410411412413414" + + "415416418419421422423425426427428430433435438439440443444445447448449450451452454455456457" + + "458460461462463464465467468469470473475476477478479481482484487488495496505506507508509510" + + "511512513514516517518519520521522523524525526527528529530531532533534535536537538539540541" + + "542543544546547548550551552553554555556557559560561562563564565566567568569571572573574575" + + "576577579585586587588589590591592593594595596597598599600604610611612613614615619620623626" + + "630632633634636637643644646651653654655659660661662663665668669670671672673674675676677678" + + "679680681682683684685686687688690691692693694695696697698699700701703704707708712714719741" + + "743745747748751752753754755756757759760761763764765766767768769770771772773774775778780781" + + "783785786787788789790791792793794795804"; export const SANTANDER_WEIGHTS = [9, 7, 3, 1, 0, 0, 9, 7, 1, 3, 1, 9, 7, 3]; diff --git a/src/is-valid-bank-account/is-valid-bank-account.test.ts b/src/is-valid-bank-account/is-valid-bank-account.test.ts index 6269b663..554e270a 100644 --- a/src/is-valid-bank-account/is-valid-bank-account.test.ts +++ b/src/is-valid-bank-account/is-valid-bank-account.test.ts @@ -1506,12 +1506,13 @@ describe("isValidBankAccount", () => { }); test("should accept the last bank code of COMPE_CODES, which an endsWith based scan would never reach", () => { - expect(COMPE_CODES.endsWith("757")).toBe(true); - expect(BANKS.some((bank) => bank.code === "757")).toBe(true); + const lastCode = COMPE_CODES.slice(-3); + + expect(BANKS.some((bank) => bank.code === lastCode)).toBe(true); expect( isValidBankAccount({ - bankCode: "757", + bankCode: lastCode, agency: "1234", account: "123456", digit: "6", From b79cef0c46cae164fd189a5ef637a5f2551f33a7 Mon Sep 17 00:00:00 2001 From: Hyan Mandian <5044101+hyanmandian@users.noreply.github.com> Date: Sat, 12 Sep 2026 10:32:59 -0300 Subject: [PATCH 02/11] docs: fix the examples and source citations the function-by-function review flagged MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Examples that did not hold against the build (generatePis, generateVoterId, generateProcessoJuridico, generateCpf("SP"), isValidCns) now show real outputs; the formatVoterId and phone `version` descriptions say what the code does. Citations point at the documents that actually define each rule: the Receita Federal manual for the CNPJ check digits, brutils for the CPF/PIS/RENAVAM algorithms and the number-to-words grammar, Provimentos CNJ 2/2009 and 3/2009 for the certidão matrícula and CNS, Ajuste SINIEF 03/1994 (+06/2000, 20/2012, 15/2013) and IN RFB 1.009/2010 for CST, Ajuste SINIEF 03/2010 for CSOSN, the FEBRABAN cobrança layout for the bancário boleto, Resolução CONTRAN 969/2022 for the Mercosul plate, the WHATWG e-mail definition for isValidEmail, and one SINTEGRA page per state (plus the SEFAZ-GO roteiro) for isValidIe. llms.txt regenerated. --- docs/llms-full.txt | 20 ++++++------- docs/llms.txt | 2 +- docs/pt-br/utilities.md | 20 ++++++------- docs/utilities.md | 20 ++++++------- src/_internals/constants/certidao.ts | 5 +++- src/_internals/constants/number-words.ts | 2 +- .../number-to-words/number-to-words.ts | 2 +- .../convert-currency-to-words.ts | 2 +- .../convert-date-to-words.ts | 2 +- .../constants.ts | 3 +- .../convert-license-plate-to-mercosul.ts | 3 +- .../convert-number-to-words.ts | 2 +- src/format-boleto/format-boleto.ts | 3 ++ src/format-certidao/format-certidao.ts | 5 +++- src/format-cnpj/format-cnpj.ts | 1 + src/format-cpf/format-cpf.ts | 1 + src/format-pis/format-pis.ts | 1 + src/format-voter-id/format-voter-id.ts | 7 +++-- src/generate-boleto/generate-boleto.ts | 3 ++ src/generate-cnpj/generate-cnpj.ts | 1 + src/generate-cpf/generate-cpf.ts | 3 +- src/generate-pis/generate-pis.ts | 1 + src/generate-voter-id/generate-voter-id.ts | 2 +- src/get-bank-by-ispb/get-bank-by-ispb.ts | 6 ++-- src/get-boleto-info/get-boleto-info.ts | 3 ++ src/is-valid-boleto/is-valid-boleto.ts | 3 ++ src/is-valid-certidao/is-valid-certidao.ts | 5 +++- src/is-valid-cnpj/is-valid-cnpj.ts | 1 + src/is-valid-cpf/is-valid-cpf.ts | 1 + src/is-valid-csosn/constants.ts | 7 +++-- src/is-valid-csosn/is-valid-csosn.ts | 3 +- src/is-valid-cst/constants.ts | 18 ++++++++---- src/is-valid-cst/is-valid-cst.ts | 12 ++++++-- src/is-valid-email/is-valid-email.ts | 6 +++- src/is-valid-ie/is-valid-ie.ts | 29 +++++++++++++++++++ .../is-valid-mobile-phone.ts | 2 +- src/is-valid-phone/is-valid-phone.ts | 4 +-- src/is-valid-pis/is-valid-pis.ts | 1 + src/is-valid-renavam/is-valid-renavam.ts | 5 +++- src/parse-boleto/parse-boleto.ts | 3 ++ src/parse-certidao/constants.ts | 5 +++- src/parse-certidao/parse-certidao.ts | 5 +++- src/parse-cnpj/parse-cnpj.ts | 1 + src/parse-cpf/parse-cpf.ts | 1 + src/parse-pis/parse-pis.ts | 1 + 45 files changed, 166 insertions(+), 67 deletions(-) diff --git a/docs/llms-full.txt b/docs/llms-full.txt index 251cf6b3..eddb6bd5 100644 --- a/docs/llms-full.txt +++ b/docs/llms-full.txt @@ -934,7 +934,7 @@ getBankByCode('999'); // null ### getBankByIspb -Look a Brazilian bank up by its ISPB (Identificador do Sistema de Pagamentos Brasileiro), the 8 digit code published by Banco Central do Brasil in the [STR participants list](https://www.bcb.gov.br/content/estabilidadefinanceira/str1/ParticipantesSTR.csv). Unlike the COMPE code (`getBankByCode`), every SPB participant has an ISPB, including institutions with no COMPE code of their own. Accepts both `string` and `number` input, with or without leading zeros. Returns a fresh copy (typed as `Bank`) of the matching bank, or `null` when no bank has that ISPB. +Look a Brazilian bank up by its ISPB (Identificador do Sistema de Pagamentos Brasileiro), the 8 digit code published by Banco Central do Brasil in the [STR participants list](https://www.bcb.gov.br/content/estabilidadefinanceira/str1/ParticipantesSTR.csv). Every SPB participant has an ISPB, but this dataset only carries the institutions that also have a COMPE code, so an ISPB whose institution has no COMPE code of its own returns `null`. Accepts both `string` and `number` input, with or without leading zeros. Returns a fresh copy (typed as `Bank`) of the matching bank, or `null` when no bank has that ISPB. ```javascript import { getBankByIspb } from '@brazilian-utils/brazilian-utils'; @@ -1378,7 +1378,7 @@ Generate a valid random processo jurídico number according to [CNJ's definition ```javascript import { generateProcessoJuridico } from '@brazilian-utils/brazilian-utils'; -generateProcessoJuridico(); // '00020802520125150049' +generateProcessoJuridico(); // '89478643020269670326' generateProcessoJuridico({ year: 2026, court: 5 }); // string | null generateProcessoJuridico({ year: 10000 }); // null (year out of range) ``` @@ -1527,7 +1527,7 @@ Generate a valid random PIS. ```javascript import { generatePis } from '@brazilian-utils/brazilian-utils'; -generatePis(); // '12345678901' +generatePis(); // '91077906857' ``` ### getMunicipality @@ -1663,7 +1663,7 @@ differenceInBusinessDays({ from: new Date('not a date'), to: new Date() }); // n ### convertDateToWords -Formats a date as its Brazilian Portuguese "por extenso" textual representation, e.g. `"01/01/2024"` becomes `"primeiro de janeiro de dois mil e vinte e quatro"`. Accepts a `Date` (read by its local calendar date, the same convention used by `isHoliday`) or a string in `"dd/mm/yyyy"` or ISO `"yyyy-mm-dd"` format. With the default `options.style` of `"full"`, day 1 is written as "primeiro" and every other day uses the cardinal number; with `"month"`, only the month name is spelled out and the day/year are left as digits (day 1 as `"1º"`, e.g. `"2 de março de 2024"`, `"1º de janeiro de 2024"`). Month names are lowercase. In `"full"` style the year is written out as a cardinal number without the thousands comma that `convertNumberToWords`/`convertCurrencyToWords` use (`1999` reads as `"mil novecentos e noventa e nove"`, not `"mil, novecentos e noventa e nove"`), matching how a date is read aloud. `options.weekday` (default `false`) prefixes the pt-BR weekday name in lowercase followed by a comma (`"sábado, dois de março de dois mil e vinte e quatro"`), computed from the resolved calendar date. `options.case` sets the letter case of the whole result: `"lower"` (default), `"sentence"` (capitalizes only the first letter) or `"upper"` (uppercases everything, keeping accents). Invalid `case`/`style` values are ignored and the default is used; the previous `capitalize` boolean option has been removed in favor of `case: "sentence"`. February 29th is accepted on the leap years of the proleptic Gregorian calendar (divisible by 4, except centuries not divisible by 400). Returns `""` for an invalid `Date`, a malformed string, a day/month that does not exist, or a date before year 1. +Formats a date as its Brazilian Portuguese "por extenso" textual representation, e.g. `"01/01/2024"` becomes `"primeiro de janeiro de dois mil e vinte e quatro"`. Accepts a `Date` (read by its local calendar date, the same convention used by `isHoliday`) or a string in `"dd/mm/yyyy"` or ISO `"yyyy-mm-dd"` format. With the default `options.style` of `"full"`, day 1 is written as "primeiro" and every other day uses the cardinal number; with `"month"`, only the month name is spelled out and the day/year are left as digits (day 1 as `"1º"`, e.g. `"2 de março de 2024"`, `"1º de janeiro de 2024"`). Month names are lowercase. In `"full"` style the year is written out as a cardinal number without the thousands comma that `convertNumberToWords`/`convertCurrencyToWords` use (`1999` reads as `"mil novecentos e noventa e nove"`, not `"mil, novecentos e noventa e nove"`), matching how a date is read aloud. `options.weekday` (default `false`) prefixes the pt-BR weekday name in lowercase followed by a comma (`"sábado, dois de março de dois mil e vinte e quatro"`), computed from the resolved calendar date. `options.case` sets the letter case of the whole result: `"lower"` (default), `"sentence"` (capitalizes only the first letter) or `"upper"` (uppercases everything, keeping accents). Invalid `case`/`style` values are ignored and the default is used. February 29th is accepted on the leap years of the proleptic Gregorian calendar (divisible by 4, except centuries not divisible by 400). Returns `""` for an invalid `Date`, a malformed string, a day/month that does not exist, or a date before year 1. ```javascript import { convertDateToWords } from '@brazilian-utils/brazilian-utils'; @@ -1683,7 +1683,7 @@ convertDateToWords('29/02/1900'); // "" (1900 is not a leap year) ### formatVoterId -Format a voter ID number. Uses the 12-digit grouping `0000 0000 00 00` by default; when the sanitized value has 13 digits (São Paulo/Minas Gerais voter ids may have a 9-digit sequential number) the `0000 0000 0 00 00` grouping is used instead. +Format a voter ID number. Uses the 12-digit grouping `0000 0000 00 00` by default; the 13-digit grouping `0000 0000 0 00 00` is used only when the sanitized value has more than 12 digits **and** its federative union code (the 10th and 11th digits) is `01` (São Paulo) or `02` (Minas Gerais), the two states whose voter ids may carry a 9-digit sequential number. ```javascript import { formatVoterId } from '@brazilian-utils/brazilian-utils'; @@ -1734,7 +1734,7 @@ Check if a CNS (Cartão Nacional de Saúde) number is valid, the unique SUS (Sis ```javascript import { isValidCns } from '@brazilian-utils/brazilian-utils'; -isValidCns('123456789010001'); // true (definitive) +isValidCns('123456789010000'); // true (definitive) isValidCns('700000000000005'); // true (provisional) isValidCns('12345678901'); // false (wrong length) ``` @@ -1753,7 +1753,7 @@ formatCns('89010001', { pad: true }); // '000 0000 8901 0001' ### isValidCertidao -Check if the matrícula of a certidão de registro civil (nascimento, casamento, óbito and the other acts kept by a serventia de registro civil das pessoas naturais) is valid. The matrícula has 32 digits laid out as 6 (CNS da serventia) + 2 (acervo) + 2 (serviço) + 4 (ano) + 1 (tipo do livro) + 5 (livro) + 3 (folha) + 7 (termo) + 2 (dígitos verificadores), and both check digits are modulus 11 with weights cycling from 2 to 10 and back through 0. Accepts the usual mask characters and whitespace between/around groups. The layout and both check digits follow the Provimento CNJ 46/2015, as detailed by [ghiorzi.org](http://ghiorzi.org/DVnew.htm) and implemented by [validation-br](https://github.com/klawdyo/validation-br/blob/feat-certidao/src/certidao.ts) and [validator-docs](https://github.com/geekcom/validator-docs/blob/master/src/validator-docs/Rules/Certidao.php). +Check if the matrícula of a certidão de registro civil (nascimento, casamento, óbito and the other acts kept by a serventia de registro civil das pessoas naturais) is valid. The matrícula has 32 digits laid out as 6 (CNS da serventia) + 2 (acervo) + 2 (serviço) + 4 (ano) + 1 (tipo do livro) + 5 (livro) + 3 (folha) + 7 (termo) + 2 (dígitos verificadores), and both check digits are modulus 11 with weights cycling from 2 to 10 and back through 0. Accepts the usual mask characters and whitespace between/around groups. The layout and both check digits follow the [Provimento CNJ nº 3/2009](https://atos.cnj.jus.br/atos/detalhar/1310), whose CNS da serventia comes from the [Provimento CNJ nº 2/2009](https://atos.cnj.jus.br/atos/detalhar/1311), as detailed by [ghiorzi.org](http://ghiorzi.org/DVnew.htm) and implemented by [validation-br](https://github.com/klawdyo/validation-br/blob/feat-certidao/src/certidao.ts) and [validator-docs](https://github.com/geekcom/validator-docs/blob/master/src/validator-docs/Rules/Certidao.php). `options.accept` (part of `IsValidCertidaoOptions`) restricts which book types (the same `CertidaoType` returned by `parseCertidao`) count as valid; when given, the book-type digit must map to one of the listed types. Defaults to every type. @@ -1770,7 +1770,7 @@ isValidCertidao('104539 01 55 2013 1 00012 021 0000123 21', { accept: ['death'] ### parseCertidao -Parse the matrícula of a certidão de registro civil into its fields, returning `null` when the matrícula is not valid or when its book code is not one of the nine books defined by the Provimento. The nine books and their codes are the ones defined by the Provimento CNJ 46/2015, as listed by [ghiorzi.org](http://ghiorzi.org/DVnew.htm). +Parse the matrícula of a certidão de registro civil into its fields, returning `null` when the matrícula is not valid or when its book code is not one of the nine books defined by the Provimento. The nine books and their codes are the ones defined by the [Provimento CNJ nº 3/2009](https://atos.cnj.jus.br/atos/detalhar/1310), as listed by [ghiorzi.org](http://ghiorzi.org/DVnew.htm). ```javascript import { parseCertidao } from '@brazilian-utils/brazilian-utils'; @@ -1809,7 +1809,7 @@ The `Certidao` result carries: ### formatCertidao -Format the matrícula of a certidão de registro civil into the printed mask of the Provimento, the 32 digits grouped as 6 2 2 4 1 5 3 7 2 and separated by spaces. `options.pad` (part of `FormatCertidaoOptions`) left pads the value with zeros up to 32 digits. The mask is the one printed in the Provimento CNJ 46/2015. +Format the matrícula of a certidão de registro civil into the printed mask of the Provimento, the 32 digits grouped as 6 2 2 4 1 5 3 7 2 and separated by spaces. `options.pad` (part of `FormatCertidaoOptions`) left pads the value with zeros up to 32 digits. The mask is the one printed in the [Provimento CNJ nº 3/2009](https://atos.cnj.jus.br/atos/detalhar/1310). ```javascript import { formatCertidao } from '@brazilian-utils/brazilian-utils'; @@ -2054,7 +2054,7 @@ isValidCst('999'); // false (not in any table) ### isValidCsosn -Check if a CSOSN (Código de Situação da Operação no Simples Nacional) code is one of the 10 codes defined by Convênio ICMS 92/2015: `101`, `102`, `103`, `201`, `202`, `203`, `300`, `400`, `500` or `900`. +Check if a CSOSN (Código de Situação da Operação no Simples Nacional) code is one of the 10 codes defined by Ajuste SINIEF 03/2010: `101`, `102`, `103`, `201`, `202`, `203`, `300`, `400`, `500` or `900`. ```javascript import { isValidCsosn } from '@brazilian-utils/brazilian-utils'; diff --git a/docs/llms.txt b/docs/llms.txt index 30be2601..84389531 100644 --- a/docs/llms.txt +++ b/docs/llms.txt @@ -60,7 +60,7 @@ const { getCities } = await import('@brazilian-utils/brazilian-utils/get-cities' - [isValidNcm](https://brazilian-utils.com.br/utilities.md#isvalidncm): Check if an NCM (Nomenclatura Comum do Mercosul) code exists in the current table published by Siscomex/MDIC. - [isValidCfop](https://brazilian-utils.com.br/utilities.md#isvalidcfop): Check if a CFOP (Código Fiscal de Operações e Prestações) code exists in the official table (Ajuste SINIEF 07/2001 and updates). - [isValidCst](https://brazilian-utils.com.br/utilities.md#isvalidcst): Check if a CST (Código de Situação Tributária) code is valid for a given tax. -- [isValidCsosn](https://brazilian-utils.com.br/utilities.md#isvalidcsosn): Check if a CSOSN (Código de Situação da Operação no Simples Nacional) code is one of the 10 codes defined by Convênio ICMS 92/2015: `101`, `102`, `103`, `201`, `202`, `203`, `300`, `400`, `500` or `900`. +- [isValidCsosn](https://brazilian-utils.com.br/utilities.md#isvalidcsosn): Check if a CSOSN (Código de Situação da Operação no Simples Nacional) code is one of the 10 codes defined by Ajuste SINIEF 03/2010: `101`, `102`, `103`, `201`, `202`, `203`, `300`, `400`, `500` or `900`. ## Formatters (format*) diff --git a/docs/pt-br/utilities.md b/docs/pt-br/utilities.md index 16f1f756..6506c7ee 100644 --- a/docs/pt-br/utilities.md +++ b/docs/pt-br/utilities.md @@ -696,7 +696,7 @@ getBankByCode('999'); // null ## getBankByIspb -Busca um banco brasileiro pelo seu ISPB (Identificador do Sistema de Pagamentos Brasileiro), o código de 8 dígitos publicado pelo Banco Central do Brasil na [lista de participantes do STR](https://www.bcb.gov.br/content/estabilidadefinanceira/str1/ParticipantesSTR.csv). Diferente do código COMPE (`getBankByCode`), todo participante do SPB tem um ISPB, incluindo instituições sem código COMPE próprio. Aceita tanto `string` quanto `number`, com ou sem zeros à esquerda. Retorna uma nova cópia (tipada como `Bank`) do banco correspondente, ou `null` quando nenhum banco tem esse ISPB. +Busca um banco brasileiro pelo seu ISPB (Identificador do Sistema de Pagamentos Brasileiro), o código de 8 dígitos publicado pelo Banco Central do Brasil na [lista de participantes do STR](https://www.bcb.gov.br/content/estabilidadefinanceira/str1/ParticipantesSTR.csv). Todo participante do SPB tem um ISPB, mas este conjunto de dados só traz as instituições que também têm código COMPE, então um ISPB cuja instituição não tem código COMPE próprio retorna `null`. Aceita tanto `string` quanto `number`, com ou sem zeros à esquerda. Retorna uma nova cópia (tipada como `Bank`) do banco correspondente, ou `null` quando nenhum banco tem esse ISPB. ```javascript import { getBankByIspb } from '@brazilian-utils/brazilian-utils'; @@ -1140,7 +1140,7 @@ Gera um número de processo jurídico válido de acordo com a definição do [CN ```javascript import { generateProcessoJuridico } from '@brazilian-utils/brazilian-utils'; -generateProcessoJuridico(); // '00020802520125150049' +generateProcessoJuridico(); // '89478643020269670326' generateProcessoJuridico({ year: 2026, court: 5 }); // string | null generateProcessoJuridico({ year: 10000 }); // null (ano fora do intervalo) ``` @@ -1289,7 +1289,7 @@ Gera um PIS válido aleatório. ```javascript import { generatePis } from '@brazilian-utils/brazilian-utils'; -generatePis(); // '12345678901' +generatePis(); // '91077906857' ``` ## getMunicipality @@ -1425,7 +1425,7 @@ differenceInBusinessDays({ from: new Date('not a date'), to: new Date() }); // n ## convertDateToWords -Formata uma data por extenso em português do Brasil, ex.: `"01/01/2024"` vira `"primeiro de janeiro de dois mil e vinte e quatro"`. Aceita um `Date` (lido pela sua data de calendário local, a mesma convenção usada por `isHoliday`) ou uma string no formato `"dd/mm/yyyy"` ou ISO `"yyyy-mm-dd"`. Com o `options.style` padrão `"full"`, o dia 1 é escrito como "primeiro" e os demais dias usam o número cardinal; com `"month"`, só o nome do mês é escrito por extenso e o dia/ano ficam em dígitos (o dia 1 como `"1º"`, ex.: `"2 de março de 2024"`, `"1º de janeiro de 2024"`). Os nomes dos meses ficam em minúsculo. No estilo `"full"` o ano é escrito por extenso sem a vírgula de milhar que `convertNumberToWords`/`convertCurrencyToWords` usam (`1999` vira `"mil novecentos e noventa e nove"`, não `"mil, novecentos e noventa e nove"`), do jeito que uma data é lida em voz alta. `options.weekday` (padrão `false`) prefixa o nome do dia da semana em pt-BR minúsculo seguido de vírgula (`"sábado, dois de março de dois mil e vinte e quatro"`), calculado a partir da data de calendário resolvida. `options.case` define a caixa de todo o resultado: `"lower"` (padrão), `"sentence"` (só a primeira letra em maiúscula) ou `"upper"` (tudo em maiúscula, preservando os acentos). Valores inválidos de `case`/`style` são ignorados e o padrão é usado; a antiga opção booleana `capitalize` foi removida em favor de `case: "sentence"`. O dia 29 de fevereiro é aceito nos anos bissextos do calendário gregoriano proléptico (divisíveis por 4, exceto séculos não divisíveis por 400). Retorna `""` para um `Date` inválido, uma string malformada, um dia/mês que não existe ou uma data anterior ao ano 1. +Formata uma data por extenso em português do Brasil, ex.: `"01/01/2024"` vira `"primeiro de janeiro de dois mil e vinte e quatro"`. Aceita um `Date` (lido pela sua data de calendário local, a mesma convenção usada por `isHoliday`) ou uma string no formato `"dd/mm/yyyy"` ou ISO `"yyyy-mm-dd"`. Com o `options.style` padrão `"full"`, o dia 1 é escrito como "primeiro" e os demais dias usam o número cardinal; com `"month"`, só o nome do mês é escrito por extenso e o dia/ano ficam em dígitos (o dia 1 como `"1º"`, ex.: `"2 de março de 2024"`, `"1º de janeiro de 2024"`). Os nomes dos meses ficam em minúsculo. No estilo `"full"` o ano é escrito por extenso sem a vírgula de milhar que `convertNumberToWords`/`convertCurrencyToWords` usam (`1999` vira `"mil novecentos e noventa e nove"`, não `"mil, novecentos e noventa e nove"`), do jeito que uma data é lida em voz alta. `options.weekday` (padrão `false`) prefixa o nome do dia da semana em pt-BR minúsculo seguido de vírgula (`"sábado, dois de março de dois mil e vinte e quatro"`), calculado a partir da data de calendário resolvida. `options.case` define a caixa de todo o resultado: `"lower"` (padrão), `"sentence"` (só a primeira letra em maiúscula) ou `"upper"` (tudo em maiúscula, preservando os acentos). Valores inválidos de `case`/`style` são ignorados e o padrão é usado. O dia 29 de fevereiro é aceito nos anos bissextos do calendário gregoriano proléptico (divisíveis por 4, exceto séculos não divisíveis por 400). Retorna `""` para um `Date` inválido, uma string malformada, um dia/mês que não existe ou uma data anterior ao ano 1. ```javascript import { convertDateToWords } from '@brazilian-utils/brazilian-utils'; @@ -1445,7 +1445,7 @@ convertDateToWords('29/02/1900'); // "" (1900 não é bissexto) ## formatVoterId -Formata um título de eleitor. Usa por padrão o agrupamento de 12 dígitos `0000 0000 00 00`; quando o valor sanitizado tem 13 dígitos (títulos de São Paulo/Minas Gerais podem ter um número sequencial de 9 dígitos) é usado o agrupamento `0000 0000 0 00 00`. +Formata um título de eleitor. Usa por padrão o agrupamento de 12 dígitos `0000 0000 00 00`; o agrupamento de 13 dígitos `0000 0000 0 00 00` só é usado quando o valor sanitizado tem mais de 12 dígitos **e** o código de unidade federativa (o 10º e o 11º dígitos) é `01` (São Paulo) ou `02` (Minas Gerais), os dois estados cujos títulos podem ter um número sequencial de 9 dígitos. ```javascript import { formatVoterId } from '@brazilian-utils/brazilian-utils'; @@ -1496,7 +1496,7 @@ Verifica se um número de CNS (Cartão Nacional de Saúde) é válido, o identif ```javascript import { isValidCns } from '@brazilian-utils/brazilian-utils'; -isValidCns('123456789010001'); // true (definitivo) +isValidCns('123456789010000'); // true (definitivo) isValidCns('700000000000005'); // true (provisório) isValidCns('12345678901'); // false (tamanho inválido) ``` @@ -1515,7 +1515,7 @@ formatCns('89010001', { pad: true }); // '000 0000 8901 0001' ## isValidCertidao -Verifica se a matrícula de uma certidão de registro civil (nascimento, casamento, óbito e os demais atos mantidos por uma serventia de registro civil das pessoas naturais) é válida. A matrícula tem 32 dígitos distribuídos em 6 (CNS da serventia) + 2 (acervo) + 2 (serviço) + 4 (ano) + 1 (tipo do livro) + 5 (livro) + 3 (folha) + 7 (termo) + 2 (dígitos verificadores), e os dois dígitos verificadores usam módulo 11 com pesos ciclando de 2 a 10 e voltando por 0. Aceita os caracteres de máscara usuais e espaços entre e ao redor dos grupos. O layout e os dois dígitos verificadores seguem o Provimento CNJ 46/2015, detalhado em [ghiorzi.org](http://ghiorzi.org/DVnew.htm) e implementado pelo [validation-br](https://github.com/klawdyo/validation-br/blob/feat-certidao/src/certidao.ts) e pelo [validator-docs](https://github.com/geekcom/validator-docs/blob/master/src/validator-docs/Rules/Certidao.php). +Verifica se a matrícula de uma certidão de registro civil (nascimento, casamento, óbito e os demais atos mantidos por uma serventia de registro civil das pessoas naturais) é válida. A matrícula tem 32 dígitos distribuídos em 6 (CNS da serventia) + 2 (acervo) + 2 (serviço) + 4 (ano) + 1 (tipo do livro) + 5 (livro) + 3 (folha) + 7 (termo) + 2 (dígitos verificadores), e os dois dígitos verificadores usam módulo 11 com pesos ciclando de 2 a 10 e voltando por 0. Aceita os caracteres de máscara usuais e espaços entre e ao redor dos grupos. O layout e os dois dígitos verificadores seguem o [Provimento CNJ nº 3/2009](https://atos.cnj.jus.br/atos/detalhar/1310), cujo CNS da serventia vem do [Provimento CNJ nº 2/2009](https://atos.cnj.jus.br/atos/detalhar/1311), detalhado em [ghiorzi.org](http://ghiorzi.org/DVnew.htm) e implementado pelo [validation-br](https://github.com/klawdyo/validation-br/blob/feat-certidao/src/certidao.ts) e pelo [validator-docs](https://github.com/geekcom/validator-docs/blob/master/src/validator-docs/Rules/Certidao.php). `options.accept` (parte de `IsValidCertidaoOptions`) restringe quais tipos de livro (o mesmo `CertidaoType` retornado por `parseCertidao`) contam como válidos; quando informado, o dígito do tipo de livro precisa corresponder a um dos tipos listados. O padrão é aceitar todos os tipos. @@ -1532,7 +1532,7 @@ isValidCertidao('104539 01 55 2013 1 00012 021 0000123 21', { accept: ['death'] ## parseCertidao -Extrai os campos da matrícula de uma certidão de registro civil, retornando `null` quando a matrícula é inválida ou quando o código do livro não é um dos nove livros definidos pelo Provimento. Os nove livros e seus códigos são os definidos pelo Provimento CNJ 46/2015, conforme listados em [ghiorzi.org](http://ghiorzi.org/DVnew.htm). +Extrai os campos da matrícula de uma certidão de registro civil, retornando `null` quando a matrícula é inválida ou quando o código do livro não é um dos nove livros definidos pelo Provimento. Os nove livros e seus códigos são os definidos pelo [Provimento CNJ nº 3/2009](https://atos.cnj.jus.br/atos/detalhar/1310), conforme listados em [ghiorzi.org](http://ghiorzi.org/DVnew.htm). ```javascript import { parseCertidao } from '@brazilian-utils/brazilian-utils'; @@ -1571,7 +1571,7 @@ O resultado `Certidao` traz: ## formatCertidao -Formata a matrícula de uma certidão de registro civil na máscara impressa do Provimento, os 32 dígitos agrupados em 6 2 2 4 1 5 3 7 2 e separados por espaços. `options.pad` (parte de `FormatCertidaoOptions`) preenche o valor com zeros à esquerda até 32 dígitos. A máscara é a impressa no Provimento CNJ 46/2015. +Formata a matrícula de uma certidão de registro civil na máscara impressa do Provimento, os 32 dígitos agrupados em 6 2 2 4 1 5 3 7 2 e separados por espaços. `options.pad` (parte de `FormatCertidaoOptions`) preenche o valor com zeros à esquerda até 32 dígitos. A máscara é a impressa no [Provimento CNJ nº 3/2009](https://atos.cnj.jus.br/atos/detalhar/1310). ```javascript import { formatCertidao } from '@brazilian-utils/brazilian-utils'; @@ -1816,7 +1816,7 @@ isValidCst('999'); // false (não existe em nenhuma tabela) ## isValidCsosn -Valida se um código de CSOSN (Código de Situação da Operação no Simples Nacional) é um dos 10 códigos definidos pelo Convênio ICMS 92/2015: `101`, `102`, `103`, `201`, `202`, `203`, `300`, `400`, `500` ou `900`. +Valida se um código de CSOSN (Código de Situação da Operação no Simples Nacional) é um dos 10 códigos definidos pelo Ajuste SINIEF 03/2010: `101`, `102`, `103`, `201`, `202`, `203`, `300`, `400`, `500` ou `900`. ```javascript import { isValidCsosn } from '@brazilian-utils/brazilian-utils'; diff --git a/docs/utilities.md b/docs/utilities.md index bd68b19f..c8655b0f 100644 --- a/docs/utilities.md +++ b/docs/utilities.md @@ -696,7 +696,7 @@ getBankByCode('999'); // null ## getBankByIspb -Look a Brazilian bank up by its ISPB (Identificador do Sistema de Pagamentos Brasileiro), the 8 digit code published by Banco Central do Brasil in the [STR participants list](https://www.bcb.gov.br/content/estabilidadefinanceira/str1/ParticipantesSTR.csv). Unlike the COMPE code (`getBankByCode`), every SPB participant has an ISPB, including institutions with no COMPE code of their own. Accepts both `string` and `number` input, with or without leading zeros. Returns a fresh copy (typed as `Bank`) of the matching bank, or `null` when no bank has that ISPB. +Look a Brazilian bank up by its ISPB (Identificador do Sistema de Pagamentos Brasileiro), the 8 digit code published by Banco Central do Brasil in the [STR participants list](https://www.bcb.gov.br/content/estabilidadefinanceira/str1/ParticipantesSTR.csv). Every SPB participant has an ISPB, but this dataset only carries the institutions that also have a COMPE code, so an ISPB whose institution has no COMPE code of its own returns `null`. Accepts both `string` and `number` input, with or without leading zeros. Returns a fresh copy (typed as `Bank`) of the matching bank, or `null` when no bank has that ISPB. ```javascript import { getBankByIspb } from '@brazilian-utils/brazilian-utils'; @@ -1140,7 +1140,7 @@ Generate a valid random processo jurídico number according to [CNJ's definition ```javascript import { generateProcessoJuridico } from '@brazilian-utils/brazilian-utils'; -generateProcessoJuridico(); // '00020802520125150049' +generateProcessoJuridico(); // '89478643020269670326' generateProcessoJuridico({ year: 2026, court: 5 }); // string | null generateProcessoJuridico({ year: 10000 }); // null (year out of range) ``` @@ -1289,7 +1289,7 @@ Generate a valid random PIS. ```javascript import { generatePis } from '@brazilian-utils/brazilian-utils'; -generatePis(); // '12345678901' +generatePis(); // '91077906857' ``` ## getMunicipality @@ -1425,7 +1425,7 @@ differenceInBusinessDays({ from: new Date('not a date'), to: new Date() }); // n ## convertDateToWords -Formats a date as its Brazilian Portuguese "por extenso" textual representation, e.g. `"01/01/2024"` becomes `"primeiro de janeiro de dois mil e vinte e quatro"`. Accepts a `Date` (read by its local calendar date, the same convention used by `isHoliday`) or a string in `"dd/mm/yyyy"` or ISO `"yyyy-mm-dd"` format. With the default `options.style` of `"full"`, day 1 is written as "primeiro" and every other day uses the cardinal number; with `"month"`, only the month name is spelled out and the day/year are left as digits (day 1 as `"1º"`, e.g. `"2 de março de 2024"`, `"1º de janeiro de 2024"`). Month names are lowercase. In `"full"` style the year is written out as a cardinal number without the thousands comma that `convertNumberToWords`/`convertCurrencyToWords` use (`1999` reads as `"mil novecentos e noventa e nove"`, not `"mil, novecentos e noventa e nove"`), matching how a date is read aloud. `options.weekday` (default `false`) prefixes the pt-BR weekday name in lowercase followed by a comma (`"sábado, dois de março de dois mil e vinte e quatro"`), computed from the resolved calendar date. `options.case` sets the letter case of the whole result: `"lower"` (default), `"sentence"` (capitalizes only the first letter) or `"upper"` (uppercases everything, keeping accents). Invalid `case`/`style` values are ignored and the default is used; the previous `capitalize` boolean option has been removed in favor of `case: "sentence"`. February 29th is accepted on the leap years of the proleptic Gregorian calendar (divisible by 4, except centuries not divisible by 400). Returns `""` for an invalid `Date`, a malformed string, a day/month that does not exist, or a date before year 1. +Formats a date as its Brazilian Portuguese "por extenso" textual representation, e.g. `"01/01/2024"` becomes `"primeiro de janeiro de dois mil e vinte e quatro"`. Accepts a `Date` (read by its local calendar date, the same convention used by `isHoliday`) or a string in `"dd/mm/yyyy"` or ISO `"yyyy-mm-dd"` format. With the default `options.style` of `"full"`, day 1 is written as "primeiro" and every other day uses the cardinal number; with `"month"`, only the month name is spelled out and the day/year are left as digits (day 1 as `"1º"`, e.g. `"2 de março de 2024"`, `"1º de janeiro de 2024"`). Month names are lowercase. In `"full"` style the year is written out as a cardinal number without the thousands comma that `convertNumberToWords`/`convertCurrencyToWords` use (`1999` reads as `"mil novecentos e noventa e nove"`, not `"mil, novecentos e noventa e nove"`), matching how a date is read aloud. `options.weekday` (default `false`) prefixes the pt-BR weekday name in lowercase followed by a comma (`"sábado, dois de março de dois mil e vinte e quatro"`), computed from the resolved calendar date. `options.case` sets the letter case of the whole result: `"lower"` (default), `"sentence"` (capitalizes only the first letter) or `"upper"` (uppercases everything, keeping accents). Invalid `case`/`style` values are ignored and the default is used. February 29th is accepted on the leap years of the proleptic Gregorian calendar (divisible by 4, except centuries not divisible by 400). Returns `""` for an invalid `Date`, a malformed string, a day/month that does not exist, or a date before year 1. ```javascript import { convertDateToWords } from '@brazilian-utils/brazilian-utils'; @@ -1445,7 +1445,7 @@ convertDateToWords('29/02/1900'); // "" (1900 is not a leap year) ## formatVoterId -Format a voter ID number. Uses the 12-digit grouping `0000 0000 00 00` by default; when the sanitized value has 13 digits (São Paulo/Minas Gerais voter ids may have a 9-digit sequential number) the `0000 0000 0 00 00` grouping is used instead. +Format a voter ID number. Uses the 12-digit grouping `0000 0000 00 00` by default; the 13-digit grouping `0000 0000 0 00 00` is used only when the sanitized value has more than 12 digits **and** its federative union code (the 10th and 11th digits) is `01` (São Paulo) or `02` (Minas Gerais), the two states whose voter ids may carry a 9-digit sequential number. ```javascript import { formatVoterId } from '@brazilian-utils/brazilian-utils'; @@ -1496,7 +1496,7 @@ Check if a CNS (Cartão Nacional de Saúde) number is valid, the unique SUS (Sis ```javascript import { isValidCns } from '@brazilian-utils/brazilian-utils'; -isValidCns('123456789010001'); // true (definitive) +isValidCns('123456789010000'); // true (definitive) isValidCns('700000000000005'); // true (provisional) isValidCns('12345678901'); // false (wrong length) ``` @@ -1515,7 +1515,7 @@ formatCns('89010001', { pad: true }); // '000 0000 8901 0001' ## isValidCertidao -Check if the matrícula of a certidão de registro civil (nascimento, casamento, óbito and the other acts kept by a serventia de registro civil das pessoas naturais) is valid. The matrícula has 32 digits laid out as 6 (CNS da serventia) + 2 (acervo) + 2 (serviço) + 4 (ano) + 1 (tipo do livro) + 5 (livro) + 3 (folha) + 7 (termo) + 2 (dígitos verificadores), and both check digits are modulus 11 with weights cycling from 2 to 10 and back through 0. Accepts the usual mask characters and whitespace between/around groups. The layout and both check digits follow the Provimento CNJ 46/2015, as detailed by [ghiorzi.org](http://ghiorzi.org/DVnew.htm) and implemented by [validation-br](https://github.com/klawdyo/validation-br/blob/feat-certidao/src/certidao.ts) and [validator-docs](https://github.com/geekcom/validator-docs/blob/master/src/validator-docs/Rules/Certidao.php). +Check if the matrícula of a certidão de registro civil (nascimento, casamento, óbito and the other acts kept by a serventia de registro civil das pessoas naturais) is valid. The matrícula has 32 digits laid out as 6 (CNS da serventia) + 2 (acervo) + 2 (serviço) + 4 (ano) + 1 (tipo do livro) + 5 (livro) + 3 (folha) + 7 (termo) + 2 (dígitos verificadores), and both check digits are modulus 11 with weights cycling from 2 to 10 and back through 0. Accepts the usual mask characters and whitespace between/around groups. The layout and both check digits follow the [Provimento CNJ nº 3/2009](https://atos.cnj.jus.br/atos/detalhar/1310), whose CNS da serventia comes from the [Provimento CNJ nº 2/2009](https://atos.cnj.jus.br/atos/detalhar/1311), as detailed by [ghiorzi.org](http://ghiorzi.org/DVnew.htm) and implemented by [validation-br](https://github.com/klawdyo/validation-br/blob/feat-certidao/src/certidao.ts) and [validator-docs](https://github.com/geekcom/validator-docs/blob/master/src/validator-docs/Rules/Certidao.php). `options.accept` (part of `IsValidCertidaoOptions`) restricts which book types (the same `CertidaoType` returned by `parseCertidao`) count as valid; when given, the book-type digit must map to one of the listed types. Defaults to every type. @@ -1532,7 +1532,7 @@ isValidCertidao('104539 01 55 2013 1 00012 021 0000123 21', { accept: ['death'] ## parseCertidao -Parse the matrícula of a certidão de registro civil into its fields, returning `null` when the matrícula is not valid or when its book code is not one of the nine books defined by the Provimento. The nine books and their codes are the ones defined by the Provimento CNJ 46/2015, as listed by [ghiorzi.org](http://ghiorzi.org/DVnew.htm). +Parse the matrícula of a certidão de registro civil into its fields, returning `null` when the matrícula is not valid or when its book code is not one of the nine books defined by the Provimento. The nine books and their codes are the ones defined by the [Provimento CNJ nº 3/2009](https://atos.cnj.jus.br/atos/detalhar/1310), as listed by [ghiorzi.org](http://ghiorzi.org/DVnew.htm). ```javascript import { parseCertidao } from '@brazilian-utils/brazilian-utils'; @@ -1571,7 +1571,7 @@ The `Certidao` result carries: ## formatCertidao -Format the matrícula of a certidão de registro civil into the printed mask of the Provimento, the 32 digits grouped as 6 2 2 4 1 5 3 7 2 and separated by spaces. `options.pad` (part of `FormatCertidaoOptions`) left pads the value with zeros up to 32 digits. The mask is the one printed in the Provimento CNJ 46/2015. +Format the matrícula of a certidão de registro civil into the printed mask of the Provimento, the 32 digits grouped as 6 2 2 4 1 5 3 7 2 and separated by spaces. `options.pad` (part of `FormatCertidaoOptions`) left pads the value with zeros up to 32 digits. The mask is the one printed in the [Provimento CNJ nº 3/2009](https://atos.cnj.jus.br/atos/detalhar/1310). ```javascript import { formatCertidao } from '@brazilian-utils/brazilian-utils'; @@ -1816,7 +1816,7 @@ isValidCst('999'); // false (not in any table) ## isValidCsosn -Check if a CSOSN (Código de Situação da Operação no Simples Nacional) code is one of the 10 codes defined by Convênio ICMS 92/2015: `101`, `102`, `103`, `201`, `202`, `203`, `300`, `400`, `500` or `900`. +Check if a CSOSN (Código de Situação da Operação no Simples Nacional) code is one of the 10 codes defined by Ajuste SINIEF 03/2010: `101`, `102`, `103`, `201`, `202`, `203`, `300`, `400`, `500` or `900`. ```javascript import { isValidCsosn } from '@brazilian-utils/brazilian-utils'; diff --git a/src/_internals/constants/certidao.ts b/src/_internals/constants/certidao.ts index 9236c671..99fc5ac6 100644 --- a/src/_internals/constants/certidao.ts +++ b/src/_internals/constants/certidao.ts @@ -3,7 +3,10 @@ * 6 (CNS da serventia) + 2 (acervo) + 2 (serviço) + 4 (ano) + 1 (tipo do livro) + 5 (livro) + * 3 (folha) + 7 (termo) + 2 (dígitos verificadores). * - * @see Official: Provimento CNJ 46/2015, art. 1º and Anexo (Código Nacional de Serventias). + * @see Official: https://atos.cnj.jus.br/atos/detalhar/1310 Provimento CNJ nº 3, de 17/11/2009, + * which instituted the modelo único de certidão and its 32 digit matrícula. + * @see Official: https://atos.cnj.jus.br/atos/detalhar/1311 Provimento CNJ nº 2, de 27/04/2009, + * which instituted the Código Nacional de Serventias (CNS). * @see Based on: http://ghiorzi.org/DVnew.htm Worked example of the two check digits * (sums 288 and 309). * @see Based on: https://github.com/klawdyo/validation-br/blob/feat-certidao/src/certidao.ts diff --git a/src/_internals/constants/number-words.ts b/src/_internals/constants/number-words.ts index 000b4a91..4d6b5b15 100644 --- a/src/_internals/constants/number-words.ts +++ b/src/_internals/constants/number-words.ts @@ -2,7 +2,7 @@ * Portuguese (pt-BR) number-to-words tables, shared by `numberToWords` and by every public * "por extenso" formatter (`convertNumberToWords`, `convertCurrencyToWords`, `convertDateToWords`). * - * @see https://github.com/brazilian-utils/python/blob/main/brutils/currency.py + * @see Based on: https://github.com/brazilian-utils/python/blob/main/brutils/currency.py * "catorze" (not "quatorze") is used for 14, matching num2words pt_BR and brutils. */ diff --git a/src/_internals/number-to-words/number-to-words.ts b/src/_internals/number-to-words/number-to-words.ts index c7e1f9e0..d92e2e5e 100644 --- a/src/_internals/number-to-words/number-to-words.ts +++ b/src/_internals/number-to-words/number-to-words.ts @@ -104,7 +104,7 @@ const isRoundHundred = (value: number): boolean => value % 100 === 0; * numberToWords(2000, { gender: "feminine" }); // "duas mil" * ``` * - * @see https://github.com/brazilian-utils/python/blob/main/brutils/currency.py + * @see Based on: https://github.com/brazilian-utils/python/blob/main/brutils/currency.py */ export const numberToWords = (value: number, options?: NumberToWordsOptions): string => { if (value === 0) return ZERO_WORD; diff --git a/src/convert-currency-to-words/convert-currency-to-words.ts b/src/convert-currency-to-words/convert-currency-to-words.ts index eaab6d91..4d49bf2e 100644 --- a/src/convert-currency-to-words/convert-currency-to-words.ts +++ b/src/convert-currency-to-words/convert-currency-to-words.ts @@ -48,7 +48,7 @@ const endsInMillionScale = (words: string): boolean => * convertCurrencyToWords(1000, { case: "upper" }); // "MIL REAIS" * ``` * - * @see https://github.com/brazilian-utils/python/blob/main/brutils/currency.py + * @see Based on: https://github.com/brazilian-utils/python/blob/main/brutils/currency.py */ export const convertCurrencyToWords = ( value: number, diff --git a/src/convert-date-to-words/convert-date-to-words.ts b/src/convert-date-to-words/convert-date-to-words.ts index 300daf25..9e66f223 100644 --- a/src/convert-date-to-words/convert-date-to-words.ts +++ b/src/convert-date-to-words/convert-date-to-words.ts @@ -69,7 +69,7 @@ const getWeekdayIndex = (year: number, month: number, day: number): number => { * convertDateToWords("invalid"); // "" * ``` * - * @see https://github.com/brazilian-utils/python/blob/main/brutils/date_utils.py + * @see Based on: https://github.com/brazilian-utils/python/blob/main/brutils/date_utils.py */ export const convertDateToWords = ( value: Date | string, diff --git a/src/convert-license-plate-to-mercosul/constants.ts b/src/convert-license-plate-to-mercosul/constants.ts index 08d1bc4a..9bdf8f86 100644 --- a/src/convert-license-plate-to-mercosul/constants.ts +++ b/src/convert-license-plate-to-mercosul/constants.ts @@ -2,7 +2,8 @@ * Official digit to letter conversion table used to turn an old format plate's 5th character * into the Mercosul format's embedded letter (0=A, 1=B, ..., 9=J). * - * @see Official: https://www.gov.br/transportes/pt-br/assuntos/transito (Resolução CONTRAN 780/2019, anexo) + * @see Official: https://www.gov.br/transportes/pt-br/assuntos/transito/conteudo-contran/resolucoes/resolucao9692022.pdf + * Resolução CONTRAN nº 969/2022, art. 2º § 4º and Anexo II. */ export const DIGIT_TO_MERCOSUL_LETTER: Record = { "0": "A", diff --git a/src/convert-license-plate-to-mercosul/convert-license-plate-to-mercosul.ts b/src/convert-license-plate-to-mercosul/convert-license-plate-to-mercosul.ts index 9f418a5e..61fa5169 100644 --- a/src/convert-license-plate-to-mercosul/convert-license-plate-to-mercosul.ts +++ b/src/convert-license-plate-to-mercosul/convert-license-plate-to-mercosul.ts @@ -20,7 +20,8 @@ import { DIGIT_TO_MERCOSUL_LETTER } from "./constants"; * convertLicensePlateToMercosul("invalid"); // "" * ``` * - * @see Official: https://www.gov.br/transportes/pt-br/assuntos/transito (Resolução CONTRAN 780/2019, anexo) + * @see Official: https://www.gov.br/transportes/pt-br/assuntos/transito/conteudo-contran/resolucoes/resolucao9692022.pdf + * Resolução CONTRAN nº 969/2022, art. 2º § 4º and Anexo II. */ export const convertLicensePlateToMercosul = (value: string): string => { if (getFormatLicensePlate(value) !== "LLLNNNN") return ""; diff --git a/src/convert-number-to-words/convert-number-to-words.ts b/src/convert-number-to-words/convert-number-to-words.ts index 61ef7e0d..64bb557e 100644 --- a/src/convert-number-to-words/convert-number-to-words.ts +++ b/src/convert-number-to-words/convert-number-to-words.ts @@ -42,7 +42,7 @@ export type ConvertNumberToWordsOptions = { * convertNumberToWords(NaN); // "" * ``` * - * @see https://github.com/brazilian-utils/python/blob/main/brutils/currency.py + * @see Based on: https://github.com/brazilian-utils/python/blob/main/brutils/currency.py */ export const convertNumberToWords = ( value: number, diff --git a/src/format-boleto/format-boleto.ts b/src/format-boleto/format-boleto.ts index ac435661..ce89d16b 100644 --- a/src/format-boleto/format-boleto.ts +++ b/src/format-boleto/format-boleto.ts @@ -34,6 +34,9 @@ export type FormatBoletoOptions = { * ``` * * @see Official: https://cmsarquivos.febraban.org.br/Arquivos/documentos/PDF/Layout%20-%20C%C3%B3digo%20de%20Barras%20-%20Vers%C3%A3o%208%20-%2011_05_2026.pdf + * @see Official: https://portal.febraban.org.br/pagina/3166/33/pt-br/layout-cobranca FEBRABAN, + * "Layout Padrão de Cobrança / Especificações Técnicas para Cobrança", the cobrança bancária + * layout behind the 47 digit linha digitável and its fator de vencimento. */ export const formatBoleto = (value: string | number, options?: FormatBoletoOptions): string => { if (isNullish(value)) return ""; diff --git a/src/format-certidao/format-certidao.ts b/src/format-certidao/format-certidao.ts index 0c8e6895..1e1bbf3a 100644 --- a/src/format-certidao/format-certidao.ts +++ b/src/format-certidao/format-certidao.ts @@ -30,7 +30,10 @@ export type FormatCertidaoOptions = { * // "000000 01 55 2010 1 00020 112 0000120 87" * ``` * - * @see Official: Provimento CNJ 46/2015, art. 1º and Anexo (Código Nacional de Serventias). + * @see Official: https://atos.cnj.jus.br/atos/detalhar/1310 Provimento CNJ nº 3, de 17/11/2009, + * which instituted the modelo único de certidão and its 32 digit matrícula. + * @see Official: https://atos.cnj.jus.br/atos/detalhar/1311 Provimento CNJ nº 2, de 27/04/2009, + * which instituted the Código Nacional de Serventias (CNS). * @see Based on: http://ghiorzi.org/DVnew.htm Worked example of the two check digits * (sums 288 and 309). * @see Based on: https://github.com/klawdyo/validation-br/blob/feat-certidao/src/certidao.ts diff --git a/src/format-cnpj/format-cnpj.ts b/src/format-cnpj/format-cnpj.ts index db4de68a..ba966cab 100644 --- a/src/format-cnpj/format-cnpj.ts +++ b/src/format-cnpj/format-cnpj.ts @@ -43,6 +43,7 @@ const sanitize = (value: string | number, version?: FormatCnpjOptions["version"] * ``` * * @see Official: https://www.gov.br/receitafederal/pt-br/assuntos/orientacao-tributaria/cadastros/cnpj + * @see Official: https://www.gov.br/receitafederal/pt-br/centrais-de-conteudo/publicacoes/documentos-tecnicos/cnpj/manual-dv-cnpj.pdf * @see Official: https://www.gov.br/receitafederal/pt-br/acesso-a-informacao/acoes-e-programas/programas-e-atividades/cnpj-alfanumerico */ export const formatCnpj = (value: string | number, options?: FormatCnpjOptions): string => { diff --git a/src/format-cpf/format-cpf.ts b/src/format-cpf/format-cpf.ts index 5c74ecc6..def7520d 100644 --- a/src/format-cpf/format-cpf.ts +++ b/src/format-cpf/format-cpf.ts @@ -29,6 +29,7 @@ export type FormatCpfOptions = { * ``` * * @see Official: https://www.gov.br/receitafederal/pt-br/assuntos/meu-cpf + * @see Based on: https://github.com/brazilian-utils/brutils-python/blob/main/brutils/cpf.py */ export const formatCpf = (value: string | number, options?: FormatCpfOptions): string => { if (isNullish(value)) return ""; diff --git a/src/format-pis/format-pis.ts b/src/format-pis/format-pis.ts index a0ecfca4..b45dbc2b 100644 --- a/src/format-pis/format-pis.ts +++ b/src/format-pis/format-pis.ts @@ -24,6 +24,7 @@ export type FormatPisOptions = { * ``` * * @see Official: https://www.gov.br/inss/pt-br/direitos-e-deveres/inscricao-e-contribuicao/inscricao + * @see Based on: https://github.com/brazilian-utils/brutils-python/blob/main/brutils/pis.py */ export const formatPis = (value: string | number, options?: FormatPisOptions): string => isNullish(value) diff --git a/src/format-voter-id/format-voter-id.ts b/src/format-voter-id/format-voter-id.ts index 5aca945f..7f2fa08d 100644 --- a/src/format-voter-id/format-voter-id.ts +++ b/src/format-voter-id/format-voter-id.ts @@ -12,9 +12,10 @@ const LENGTH = 12; /** * Formats a Brazilian voter id (título de eleitor) for display. * - * Uses the 12-digit grouping "0000 0000 00 00" by default. When the sanitized value has more - * than 12 digits (São Paulo/Minas Gerais voter ids may have a 9-digit sequential number) the - * 13-digit grouping "0000 0000 0 00 00" is used instead. + * Uses the 12-digit grouping "0000 0000 00 00" by default. The 13-digit grouping + * "0000 0000 0 00 00" is used only when the sanitized value has more than 12 digits and its + * federative union code (the 10th and 11th digits) is "01" (São Paulo) or "02" (Minas Gerais), + * the two states whose voter ids may carry a 9-digit sequential number. * * @param {string|number} value - The voter id value to be formatted. * @returns {string} The formatted voter id string. diff --git a/src/generate-boleto/generate-boleto.ts b/src/generate-boleto/generate-boleto.ts index 4abe0ae0..7b0cb365 100644 --- a/src/generate-boleto/generate-boleto.ts +++ b/src/generate-boleto/generate-boleto.ts @@ -73,6 +73,9 @@ const generateArrecadacao = (): string => { * ``` * * @see Official: https://cmsarquivos.febraban.org.br/Arquivos/documentos/PDF/Layout%20-%20C%C3%B3digo%20de%20Barras%20-%20Vers%C3%A3o%208%20-%2011_05_2026.pdf + * @see Official: https://portal.febraban.org.br/pagina/3166/33/pt-br/layout-cobranca FEBRABAN, + * "Layout Padrão de Cobrança / Especificações Técnicas para Cobrança", the cobrança bancária + * layout behind the 47 digit linha digitável and its fator de vencimento. */ export const generateBoleto = (options?: GenerateBoletoOptions): string => options?.type === "arrecadacao" ? generateArrecadacao() : generateBancario(); diff --git a/src/generate-cnpj/generate-cnpj.ts b/src/generate-cnpj/generate-cnpj.ts index 3d2af809..4f583628 100644 --- a/src/generate-cnpj/generate-cnpj.ts +++ b/src/generate-cnpj/generate-cnpj.ts @@ -73,6 +73,7 @@ const generateAlphanumericCnpj = (): string => { * ``` * * @see Official: https://www.gov.br/receitafederal/pt-br/assuntos/orientacao-tributaria/cadastros/cnpj + * @see Official: https://www.gov.br/receitafederal/pt-br/centrais-de-conteudo/publicacoes/documentos-tecnicos/cnpj/manual-dv-cnpj.pdf */ export const generateCnpj = (version: 1 | 2 = 1): string => version === 1 ? generateNumericCnpj() : generateAlphanumericCnpj(); diff --git a/src/generate-cpf/generate-cpf.ts b/src/generate-cpf/generate-cpf.ts index b394e204..8746cd2c 100644 --- a/src/generate-cpf/generate-cpf.ts +++ b/src/generate-cpf/generate-cpf.ts @@ -25,10 +25,11 @@ const calculateCheckDigit = (base: string, weight: number): string => { * @example * ```typescript * generateCpf(); // "12345678909" - * generateCpf("SP"); // "12345678909" (with SP state code in 9th digit) + * generateCpf("SP"); // "12345678810" (with the SP state code, 8, in the 9th digit) * ``` * * @see Official: https://www.gov.br/receitafederal/pt-br/assuntos/meu-cpf + * @see Based on: https://github.com/brazilian-utils/brutils-python/blob/main/brutils/cpf.py */ export const generateCpf = (state?: StateCode): string => { let base = generateRandomNumber(BASE_LENGTH) + getStateCode(state); diff --git a/src/generate-pis/generate-pis.ts b/src/generate-pis/generate-pis.ts index 43d0e0be..960f6169 100644 --- a/src/generate-pis/generate-pis.ts +++ b/src/generate-pis/generate-pis.ts @@ -24,6 +24,7 @@ const calculateCheckDigit = (base: string): string => { * ``` * * @see Official: https://www.gov.br/inss/pt-br/direitos-e-deveres/inscricao-e-contribuicao/inscricao + * @see Based on: https://github.com/brazilian-utils/brutils-python/blob/main/brutils/pis.py */ export const generatePis = (): string => { let base = generateRandomNumber(10); diff --git a/src/generate-voter-id/generate-voter-id.ts b/src/generate-voter-id/generate-voter-id.ts index fd32084a..5c8632cc 100644 --- a/src/generate-voter-id/generate-voter-id.ts +++ b/src/generate-voter-id/generate-voter-id.ts @@ -15,7 +15,7 @@ import { UF_TO_VOTER_ID_CODE } from "../is-valid-voter-id/constants"; * * @example * ```typescript - * generateVoterId(); // "123456782897" (abroad, UF "28") + * generateVoterId(); // "123456782895" (abroad, UF "28") * generateVoterId("SP"); // "123456780191" (UF "01") * generateVoterId("XX" as StateCode); // falls back to "ZZ" instead of throwing * ``` diff --git a/src/get-bank-by-ispb/get-bank-by-ispb.ts b/src/get-bank-by-ispb/get-bank-by-ispb.ts index 03f794b1..757e7197 100644 --- a/src/get-bank-by-ispb/get-bank-by-ispb.ts +++ b/src/get-bank-by-ispb/get-bank-by-ispb.ts @@ -7,9 +7,9 @@ const ISPB_LENGTH = 8; /** * Looks up a Brazilian bank by its ISPB (Identificador do Sistema de Pagamentos Brasileiro), * the 8 digit code that identifies every participant of the SPB, published by Banco Central do - * Brasil in the STR (Sistema de Transferência de Reservas) participants list. Unlike the COMPE - * code (`getBankByCode`), every SPB participant has an ISPB, including institutions with no - * COMPE code of their own. + * Brasil in the STR (Sistema de Transferência de Reservas) participants list. Every SPB + * participant has an ISPB, but this dataset only carries the institutions that also have a + * COMPE code, so an ISPB whose institution has no COMPE code of its own returns `null`. * * @param {string|number} value - The bank's ISPB, with or without leading zeros. * @returns {Bank|null} A fresh copy of the matching bank, or `null` when no bank has that ISPB. diff --git a/src/get-boleto-info/get-boleto-info.ts b/src/get-boleto-info/get-boleto-info.ts index b8147ccb..86a1897f 100644 --- a/src/get-boleto-info/get-boleto-info.ts +++ b/src/get-boleto-info/get-boleto-info.ts @@ -99,6 +99,9 @@ export type GetBoletoInfoOptions = { * ``` * * @see Official: https://cmsarquivos.febraban.org.br/Arquivos/documentos/PDF/Layout%20-%20C%C3%B3digo%20de%20Barras%20-%20Vers%C3%A3o%208%20-%2011_05_2026.pdf + * @see Official: https://portal.febraban.org.br/pagina/3166/33/pt-br/layout-cobranca FEBRABAN, + * "Layout Padrão de Cobrança / Especificações Técnicas para Cobrança", the cobrança bancária + * layout behind the 47 digit linha digitável and its fator de vencimento. */ export const getBoletoInfo = ( value: string, diff --git a/src/is-valid-boleto/is-valid-boleto.ts b/src/is-valid-boleto/is-valid-boleto.ts index dbcdc05c..2ac8785e 100644 --- a/src/is-valid-boleto/is-valid-boleto.ts +++ b/src/is-valid-boleto/is-valid-boleto.ts @@ -48,6 +48,9 @@ const isValidCheckDigit = (boleto: string): boolean => { * ``` * * @see Official: https://cmsarquivos.febraban.org.br/Arquivos/documentos/PDF/Layout%20-%20C%C3%B3digo%20de%20Barras%20-%20Vers%C3%A3o%208%20-%2011_05_2026.pdf + * @see Official: https://portal.febraban.org.br/pagina/3166/33/pt-br/layout-cobranca FEBRABAN, + * "Layout Padrão de Cobrança / Especificações Técnicas para Cobrança", the cobrança bancária + * layout behind the 47 digit linha digitável and its fator de vencimento. */ export const isValidBoleto = (value: string): boolean => { if (typeof value !== "string") return false; diff --git a/src/is-valid-certidao/is-valid-certidao.ts b/src/is-valid-certidao/is-valid-certidao.ts index 2dd2dd22..8412692f 100644 --- a/src/is-valid-certidao/is-valid-certidao.ts +++ b/src/is-valid-certidao/is-valid-certidao.ts @@ -60,7 +60,10 @@ const getCheckDigit = (value: string): number => { * isValidCertidao("104539 01 55 2013 1 00012 021 0000123 21", { accept: ["death"] }); // false * ``` * - * @see Official: Provimento CNJ 46/2015, art. 1º and Anexo (Código Nacional de Serventias). + * @see Official: https://atos.cnj.jus.br/atos/detalhar/1310 Provimento CNJ nº 3, de 17/11/2009, + * which instituted the modelo único de certidão and its 32 digit matrícula. + * @see Official: https://atos.cnj.jus.br/atos/detalhar/1311 Provimento CNJ nº 2, de 27/04/2009, + * which instituted the Código Nacional de Serventias (CNS). * @see Based on: http://ghiorzi.org/DVnew.htm Worked example of the two check digits * (sums 288 and 309). * @see Based on: https://github.com/klawdyo/validation-br/blob/feat-certidao/src/certidao.ts diff --git a/src/is-valid-cnpj/is-valid-cnpj.ts b/src/is-valid-cnpj/is-valid-cnpj.ts index 034b5dfa..67ef7a3b 100644 --- a/src/is-valid-cnpj/is-valid-cnpj.ts +++ b/src/is-valid-cnpj/is-valid-cnpj.ts @@ -86,6 +86,7 @@ const isValidChecksum = (cnpj: string): boolean => { * ``` * * @see Official: https://www.gov.br/receitafederal/pt-br/assuntos/orientacao-tributaria/cadastros/cnpj + * @see Official: https://www.gov.br/receitafederal/pt-br/centrais-de-conteudo/publicacoes/documentos-tecnicos/cnpj/manual-dv-cnpj.pdf * @see Official: https://www.gov.br/receitafederal/pt-br/acesso-a-informacao/acoes-e-programas/programas-e-atividades/cnpj-alfanumerico */ export const isValidCnpj = (cnpj: string, options?: IsValidCnpjOptions): boolean => { diff --git a/src/is-valid-cpf/is-valid-cpf.ts b/src/is-valid-cpf/is-valid-cpf.ts index 1d1b4f9c..cb1c18f1 100644 --- a/src/is-valid-cpf/is-valid-cpf.ts +++ b/src/is-valid-cpf/is-valid-cpf.ts @@ -39,6 +39,7 @@ const isValidChecksum = (cpf: string): boolean => { * ``` * * @see Official: https://www.gov.br/receitafederal/pt-br/assuntos/meu-cpf + * @see Based on: https://github.com/brazilian-utils/brutils-python/blob/main/brutils/cpf.py */ export const isValidCpf = (cpf: string): boolean => { if (typeof cpf !== "string") return false; diff --git a/src/is-valid-csosn/constants.ts b/src/is-valid-csosn/constants.ts index 84c88ed7..f6f9add4 100644 --- a/src/is-valid-csosn/constants.ts +++ b/src/is-valid-csosn/constants.ts @@ -1,8 +1,9 @@ /** - * CSOSN (Código de Situação da Operação no Simples Nacional) codes, per Convênio ICMS - * 92/2015 (Anexo, Código de Situação da Operação no Simples Nacional). + * CSOSN (Código de Situação da Operação no Simples Nacional) codes, per Ajuste SINIEF + * 03/2010, which instituted the Código de Situação da Operação no Simples Nacional. * - * @see Official: https://www.confaz.fazenda.gov.br/legislacao/convenios/2015/CV092_15 + * @see Official: https://www.confaz.fazenda.gov.br/legislacao/ajustes/2010/aj_003_10 Ajuste + * SINIEF 03/2010, which instituted the CSOSN table. */ export const CSOSN_CODES = [ "101", diff --git a/src/is-valid-csosn/is-valid-csosn.ts b/src/is-valid-csosn/is-valid-csosn.ts index 86140316..01a7c4b0 100644 --- a/src/is-valid-csosn/is-valid-csosn.ts +++ b/src/is-valid-csosn/is-valid-csosn.ts @@ -10,7 +10,8 @@ import { CSOSN_CODES } from "./constants"; * @param {string|number} value - The CSOSN code to be validated. * @returns {boolean} True when the code is a known CSOSN code, false otherwise. * - * @see Official: https://www.confaz.fazenda.gov.br/legislacao/convenios/2015/CV092_15 + * @see Official: https://www.confaz.fazenda.gov.br/legislacao/ajustes/2010/aj_003_10 Ajuste + * SINIEF 03/2010, which instituted the CSOSN table. * * @example * ```typescript diff --git a/src/is-valid-cst/constants.ts b/src/is-valid-cst/constants.ts index 65169cfb..d8fd698a 100644 --- a/src/is-valid-cst/constants.ts +++ b/src/is-valid-cst/constants.ts @@ -1,10 +1,18 @@ /** - * CST (Código de Situação Tributária) code tables per tax, per Ajuste SINIEF 07/2001 (Anexo, - * Tabela B) for ICMS and IPI, and Instrução Normativa RFB n. 594/2005 (Tabelas 4.3.3 and 4.3.4) - * for PIS and COFINS. + * CST (Código de Situação Tributária) code tables per tax: Ajuste SINIEF 03/1994 with the + * Tabela A of Ajuste SINIEF 20/2012 and 15/2013 and the Tabela B of Ajuste SINIEF 06/2000 for + * ICMS, and Instrução Normativa RFB nº 1.009/2010 (Tabelas I to III) for IPI, PIS and COFINS. * - * @see Official: https://www.confaz.fazenda.gov.br/legislacao/ajustes/2001/aj007_01 - * @see Official: https://normas.receita.fazenda.gov.br/sijut2consulta/link.action?idAto=15304 + * @see Official: https://www.confaz.fazenda.gov.br/legislacao/ajustes/1994/aj_003_94 Ajuste + * SINIEF 03/1994, which instituted the ICMS CST as the two digit code AB. + * @see Official: https://www.confaz.fazenda.gov.br/legislacao/ajustes/2000/aj_006_00 Ajuste + * SINIEF 06/2000, which gives the current Tabela B (the tributação pelo ICMS digits). + * @see Official: https://www.confaz.fazenda.gov.br/legislacao/ajustes/2012/aj_020_12 Ajuste + * SINIEF 20/2012, which gives Tabela A (origem da mercadoria, 0 to 7). + * @see Official: https://www.confaz.fazenda.gov.br/legislacao/ajustes/2013/aj_015_13 Ajuste + * SINIEF 15/2013, which added origem 8 to Tabela A. + * @see Official: https://normas.receita.fazenda.gov.br/sijut2consulta/link.action?idAto=15974 + * Instrução Normativa RFB nº 1.009/2010, Tabelas I to III (CST-IPI, CST-PIS and CST-COFINS). */ export const ICMS_CST_CODES = [ "00", diff --git a/src/is-valid-cst/is-valid-cst.ts b/src/is-valid-cst/is-valid-cst.ts index f8a163b7..91194432 100644 --- a/src/is-valid-cst/is-valid-cst.ts +++ b/src/is-valid-cst/is-valid-cst.ts @@ -48,8 +48,16 @@ const isValidForTax = (digits: string, tax: "icms" | "ipi" | "pis" | "cofins"): * @returns {boolean} True when the code is valid for the given tax (or for any tax, when * `options.tax` is omitted), false otherwise. * - * @see Official: https://www.confaz.fazenda.gov.br/legislacao/ajustes/2001/aj007_01 - * @see Official: https://normas.receita.fazenda.gov.br/sijut2consulta/link.action?idAto=15304 + * @see Official: https://www.confaz.fazenda.gov.br/legislacao/ajustes/1994/aj_003_94 Ajuste + * SINIEF 03/1994, which instituted the ICMS CST as the two digit code AB. + * @see Official: https://www.confaz.fazenda.gov.br/legislacao/ajustes/2000/aj_006_00 Ajuste + * SINIEF 06/2000, which gives the current Tabela B (the tributação pelo ICMS digits). + * @see Official: https://www.confaz.fazenda.gov.br/legislacao/ajustes/2012/aj_020_12 Ajuste + * SINIEF 20/2012, which gives Tabela A (origem da mercadoria, 0 to 7). + * @see Official: https://www.confaz.fazenda.gov.br/legislacao/ajustes/2013/aj_015_13 Ajuste + * SINIEF 15/2013, which added origem 8 to Tabela A. + * @see Official: https://normas.receita.fazenda.gov.br/sijut2consulta/link.action?idAto=15974 + * Instrução Normativa RFB nº 1.009/2010, Tabelas I to III (CST-IPI, CST-PIS and CST-COFINS). * * @example * ```typescript diff --git a/src/is-valid-email/is-valid-email.ts b/src/is-valid-email/is-valid-email.ts index 12be1da7..a63d9345 100644 --- a/src/is-valid-email/is-valid-email.ts +++ b/src/is-valid-email/is-valid-email.ts @@ -14,7 +14,11 @@ const EMAIL_REGEX = * isValidEmail("test@domain.co.uk"); // true * ``` * - * @see Official: https://www.rfc-editor.org/rfc/rfc5322 + * @see Based on: https://html.spec.whatwg.org/multipage/input.html#valid-e-mail-address The + * WHATWG HTML "valid e-mail address" definition, narrowed further: the local part is limited to + * letters, digits and `_'+-.`, it may not start with a dot or contain two dots in a row, and the + * domain must carry at least one dot and end in an alphabetic label of two or more letters. It is + * a practical subset, not RFC 5322: quoted local parts and address literals are rejected. */ export const isValidEmail = (value: string): boolean => { if (typeof value !== "string") return false; diff --git a/src/is-valid-ie/is-valid-ie.ts b/src/is-valid-ie/is-valid-ie.ts index a77542bd..507eda98 100644 --- a/src/is-valid-ie/is-valid-ie.ts +++ b/src/is-valid-ie/is-valid-ie.ts @@ -518,6 +518,35 @@ const IE_VALIDATORS: Record = { * ``` * * @see Official: http://www.sintegra.gov.br/insc_est.html + * @see Official: http://www.sintegra.gov.br/Cad_Estados/cad_AC.html + * @see Official: http://www.sintegra.gov.br/Cad_Estados/cad_AL.html + * @see Official: http://www.sintegra.gov.br/Cad_Estados/cad_AM.html + * @see Official: http://www.sintegra.gov.br/Cad_Estados/cad_AP.html + * @see Official: http://www.sintegra.gov.br/Cad_Estados/cad_BA.html + * @see Official: http://www.sintegra.gov.br/Cad_Estados/cad_CE.html + * @see Official: http://www.sintegra.gov.br/Cad_Estados/cad_DF.html + * @see Official: http://www.sintegra.gov.br/Cad_Estados/cad_ES.html + * @see Official: http://www.sintegra.gov.br/Cad_Estados/cad_GO.html + * @see Official: http://www.sintegra.gov.br/Cad_Estados/cad_MA.html + * @see Official: http://www.sintegra.gov.br/Cad_Estados/cad_MG.html + * @see Official: http://www.sintegra.gov.br/Cad_Estados/cad_MS.html + * @see Official: http://www.sintegra.gov.br/Cad_Estados/cad_MT.html + * @see Official: http://www.sintegra.gov.br/Cad_Estados/cad_PA.html + * @see Official: http://www.sintegra.gov.br/Cad_Estados/cad_PB.html + * @see Official: http://www.sintegra.gov.br/Cad_Estados/cad_PE.html + * @see Official: http://www.sintegra.gov.br/Cad_Estados/cad_PI.html + * @see Official: http://www.sintegra.gov.br/Cad_Estados/cad_PR.html + * @see Official: http://www.sintegra.gov.br/Cad_Estados/cad_RJ.html + * @see Official: http://www.sintegra.gov.br/Cad_Estados/cad_RN.html + * @see Official: http://www.sintegra.gov.br/Cad_Estados/cad_RO.html + * @see Official: http://www.sintegra.gov.br/Cad_Estados/cad_RR.html + * @see Official: http://www.sintegra.gov.br/Cad_Estados/cad_RS.html + * @see Official: http://www.sintegra.gov.br/Cad_Estados/cad_SC.html + * @see Official: http://www.sintegra.gov.br/Cad_Estados/cad_SE.html + * @see Official: http://www.sintegra.gov.br/Cad_Estados/cad_SP.html + * @see Official: http://www.sintegra.gov.br/Cad_Estados/cad_TO.html + * @see Official: https://goias.gov.br/economia/roteiro-de-critica-da-inscricao-estadual-de-goias/ + * SEFAZ-GO's roteiro de crítica, the source of the Goiás prefixes and special ranges. */ export const isValidIe = (stateCode: StateCode, ie: string): boolean => { if (!stateCode || typeof stateCode !== "string") return false; diff --git a/src/is-valid-mobile-phone/is-valid-mobile-phone.ts b/src/is-valid-mobile-phone/is-valid-mobile-phone.ts index 55e446a5..76e0d40a 100644 --- a/src/is-valid-mobile-phone/is-valid-mobile-phone.ts +++ b/src/is-valid-mobile-phone/is-valid-mobile-phone.ts @@ -8,7 +8,7 @@ export type { PhoneVersion } from "../is-valid-phone/is-valid-phone"; /** Options of `isValidMobilePhone`. */ export type IsValidMobilePhoneOptions = { - /** Numbering rule to enforce: `1` the pre-2016 8 digit rule, `2` the 9 digit one (default: `2`). */ + /** Numbering rule to enforce over the 11 digit number: `1` (default) accepts 6, 7, 8 or 9 as the first number digit, `2` requires 9. */ version?: PhoneVersion; }; diff --git a/src/is-valid-phone/is-valid-phone.ts b/src/is-valid-phone/is-valid-phone.ts index 928ac32d..6f62e1c6 100644 --- a/src/is-valid-phone/is-valid-phone.ts +++ b/src/is-valid-phone/is-valid-phone.ts @@ -9,7 +9,7 @@ import { isValidMobilePhone } from "../is-valid-mobile-phone/is-valid-mobile-pho import { isValidServicePhone } from "../is-valid-service-phone/is-valid-service-phone"; import { DEFAULT_ACCEPT } from "./constants"; -/** The Brazilian mobile numbering rule to enforce: `1` the pre-2016 8 digit one, `2` the current 9 digit one. */ +/** The Brazilian mobile numbering rule to enforce over the 11 digit number: `1` the legacy one, `2` the current one. */ export type PhoneVersion = 1 | 2; /** The kinds of Brazilian phone number `isValidPhone` can accept. */ @@ -17,7 +17,7 @@ export type PhoneType = "mobile" | "landline" | "service"; /** Options of `isValidPhone`. */ export type IsValidPhoneOptions = { - /** Mobile numbering rule to enforce, see `isValidMobilePhone` (default: `2`). */ + /** Mobile numbering rule to enforce, see `isValidMobilePhone` (default: `1`). */ version?: PhoneVersion; /** Kinds of number that count as valid (default: `["mobile", "landline"]`). */ accept?: PhoneType[]; diff --git a/src/is-valid-pis/is-valid-pis.ts b/src/is-valid-pis/is-valid-pis.ts index 428b1dd5..50b5325f 100644 --- a/src/is-valid-pis/is-valid-pis.ts +++ b/src/is-valid-pis/is-valid-pis.ts @@ -19,6 +19,7 @@ import { RESERVED_NUMBERS } from "./constants"; * ``` * * @see Official: https://www.gov.br/inss/pt-br/direitos-e-deveres/inscricao-e-contribuicao/inscricao + * @see Based on: https://github.com/brazilian-utils/brutils-python/blob/main/brutils/pis.py */ export const isValidPis = (pis: string): boolean => { if (typeof pis !== "string") return false; diff --git a/src/is-valid-renavam/is-valid-renavam.ts b/src/is-valid-renavam/is-valid-renavam.ts index 35f48c3e..7331df7a 100644 --- a/src/is-valid-renavam/is-valid-renavam.ts +++ b/src/is-valid-renavam/is-valid-renavam.ts @@ -24,7 +24,10 @@ const padLeft = (input: string, padLength: number): string => * isValidRenavam("12345678901"); // false (invalid checksum) * ``` * - * @see Official: https://www.planalto.gov.br/ccivil_03/leis/l9503compilado.htm + * @see Official: https://www.planalto.gov.br/ccivil_03/leis/l9503compilado.htm The Código de + * Trânsito Brasileiro, which creates the RENAVAM registry but does not define its check digit. + * @see Based on: https://github.com/klawdyo/validation-br/blob/main/src/renavam.ts + * @see Based on: https://github.com/brazilian-utils/brutils-python/blob/main/brutils/renavam.py */ export const isValidRenavam = (renavam: string | number): boolean => { if (typeof renavam !== "string" && typeof renavam !== "number") return false; diff --git a/src/parse-boleto/parse-boleto.ts b/src/parse-boleto/parse-boleto.ts index a3a8d04a..6ce52c82 100644 --- a/src/parse-boleto/parse-boleto.ts +++ b/src/parse-boleto/parse-boleto.ts @@ -22,6 +22,9 @@ import { sanitizeToDigits } from "../_internals/sanitize-to-digits/sanitize-to-d * ``` * * @see Official: https://cmsarquivos.febraban.org.br/Arquivos/documentos/PDF/Layout%20-%20C%C3%B3digo%20de%20Barras%20-%20Vers%C3%A3o%208%20-%2011_05_2026.pdf + * @see Official: https://portal.febraban.org.br/pagina/3166/33/pt-br/layout-cobranca FEBRABAN, + * "Layout Padrão de Cobrança / Especificações Técnicas para Cobrança", the cobrança bancária + * layout behind the 47 digit linha digitável and its fator de vencimento. */ export const parseBoleto = (value: string | number): string => { if (isNullish(value)) return ""; diff --git a/src/parse-certidao/constants.ts b/src/parse-certidao/constants.ts index 02b77f21..40b5b157 100644 --- a/src/parse-certidao/constants.ts +++ b/src/parse-certidao/constants.ts @@ -5,7 +5,10 @@ * (proclamas), Livro E (demais atos), Livro E desdobrado para emancipações and Livro E * desdobrado para interdições. * - * @see Official: Provimento CNJ 46/2015, art. 1º and Anexo (Código Nacional de Serventias). + * @see Official: https://atos.cnj.jus.br/atos/detalhar/1310 Provimento CNJ nº 3, de 17/11/2009, + * which instituted the modelo único de certidão and its 32 digit matrícula. + * @see Official: https://atos.cnj.jus.br/atos/detalhar/1311 Provimento CNJ nº 2, de 27/04/2009, + * which instituted the Código Nacional de Serventias (CNS). * @see Based on: http://ghiorzi.org/DVnew.htm Description of the nine books and their codes. * @see Based on: https://github.com/Casilhero/brazilian-validators/blob/main/src/Support/CertidaoInfo.php * Reference implementation agreeing on the same nine books, in the same order. diff --git a/src/parse-certidao/parse-certidao.ts b/src/parse-certidao/parse-certidao.ts index 4eb517d3..5165ccf1 100644 --- a/src/parse-certidao/parse-certidao.ts +++ b/src/parse-certidao/parse-certidao.ts @@ -62,7 +62,10 @@ export type Certidao = { * parseCertidao("invalid"); // null * ``` * - * @see Official: Provimento CNJ 46/2015, art. 1º and Anexo (Código Nacional de Serventias). + * @see Official: https://atos.cnj.jus.br/atos/detalhar/1310 Provimento CNJ nº 3, de 17/11/2009, + * which instituted the modelo único de certidão and its 32 digit matrícula. + * @see Official: https://atos.cnj.jus.br/atos/detalhar/1311 Provimento CNJ nº 2, de 27/04/2009, + * which instituted the Código Nacional de Serventias (CNS). * @see Based on: http://ghiorzi.org/DVnew.htm Worked example of the two check digits * (sums 288 and 309). * @see Based on: https://github.com/klawdyo/validation-br/blob/feat-certidao/src/certidao.ts diff --git a/src/parse-cnpj/parse-cnpj.ts b/src/parse-cnpj/parse-cnpj.ts index e7eabb50..eb90e6dd 100644 --- a/src/parse-cnpj/parse-cnpj.ts +++ b/src/parse-cnpj/parse-cnpj.ts @@ -30,6 +30,7 @@ const sanitize = (value: string | number, version?: FormatCnpjOptions["version"] * ``` * * @see Official: https://www.gov.br/receitafederal/pt-br/assuntos/orientacao-tributaria/cadastros/cnpj + * @see Official: https://www.gov.br/receitafederal/pt-br/centrais-de-conteudo/publicacoes/documentos-tecnicos/cnpj/manual-dv-cnpj.pdf * @see Official: https://www.gov.br/receitafederal/pt-br/acesso-a-informacao/acoes-e-programas/programas-e-atividades/cnpj-alfanumerico */ export const parseCnpj = (value: string | number, options?: ParseCnpjOptions): string => diff --git a/src/parse-cpf/parse-cpf.ts b/src/parse-cpf/parse-cpf.ts index 85c05188..263aba85 100644 --- a/src/parse-cpf/parse-cpf.ts +++ b/src/parse-cpf/parse-cpf.ts @@ -14,6 +14,7 @@ import { sanitizeToDigits } from "../_internals/sanitize-to-digits/sanitize-to-d * ``` * * @see Official: https://www.gov.br/receitafederal/pt-br/assuntos/meu-cpf + * @see Based on: https://github.com/brazilian-utils/brutils-python/blob/main/brutils/cpf.py */ export const parseCpf = (value: string | number): string => isNullish(value) ? "" : sanitizeToDigits(value).slice(0, CPF_LENGTH); diff --git a/src/parse-pis/parse-pis.ts b/src/parse-pis/parse-pis.ts index 7bea6698..799fb473 100644 --- a/src/parse-pis/parse-pis.ts +++ b/src/parse-pis/parse-pis.ts @@ -14,6 +14,7 @@ import { sanitizeToDigits } from "../_internals/sanitize-to-digits/sanitize-to-d * ``` * * @see Official: https://www.gov.br/inss/pt-br/direitos-e-deveres/inscricao-e-contribuicao/inscricao + * @see Based on: https://github.com/brazilian-utils/brutils-python/blob/main/brutils/pis.py */ export const parsePis = (value: string | number): string => isNullish(value) ? "" : sanitizeToDigits(value).slice(0, PIS_LENGTH); From 73e43a5aba606ae2bafb6dee5a285efe679ddce4 Mon Sep 17 00:00:00 2001 From: Hyan Mandian <5044101+hyanmandian@users.noreply.github.com> Date: Sat, 12 Sep 2026 10:58:31 -0300 Subject: [PATCH 03/11] refactor(holidays): move the holiday date resolution to an internal helper resolveStateHolidayDate (Easter by Meeus/Jones/Butcher plus the fixed day/month rule) now lives in src/_internals with its own tests, including the malformed-entry error. The two getHolidays tests that pushed a broken entry into STATE_HOLIDAYS to reach that error are gone: getHolidays memoizes per year/state, so on Bun and Deno (one process for every test file) a property test elsewhere could warm the same cache entry first and the throw never happened. No behaviour change. --- .../resolve-state-holiday-date.test.ts | 40 +++++++++++ .../resolve-state-holiday-date.ts | 71 +++++++++++++++++++ src/get-holidays/get-holidays.test.ts | 33 --------- src/get-holidays/get-holidays.ts | 54 ++------------ 4 files changed, 116 insertions(+), 82 deletions(-) create mode 100644 src/_internals/resolve-state-holiday-date/resolve-state-holiday-date.test.ts create mode 100644 src/_internals/resolve-state-holiday-date/resolve-state-holiday-date.ts diff --git a/src/_internals/resolve-state-holiday-date/resolve-state-holiday-date.test.ts b/src/_internals/resolve-state-holiday-date/resolve-state-holiday-date.test.ts new file mode 100644 index 00000000..37e3e8c7 --- /dev/null +++ b/src/_internals/resolve-state-holiday-date/resolve-state-holiday-date.test.ts @@ -0,0 +1,40 @@ +import { describe, expect, test } from "../test/runtime"; +import { resolveStateHolidayDate } from "./resolve-state-holiday-date"; + +describe("resolveStateHolidayDate", () => { + test("should compute Easter Sunday for years across the supported range", () => { + expect(resolveStateHolidayDate(1900, { easterOffset: 0 })).toEqual(new Date(1900, 3, 15)); + expect(resolveStateHolidayDate(2000, { easterOffset: 0 })).toEqual(new Date(2000, 3, 23)); + expect(resolveStateHolidayDate(2020, { easterOffset: 0 })).toEqual(new Date(2020, 3, 12)); + expect(resolveStateHolidayDate(2024, { easterOffset: 0 })).toEqual(new Date(2024, 2, 31)); + expect(resolveStateHolidayDate(2038, { easterOffset: 0 })).toEqual(new Date(2038, 3, 25)); + expect(resolveStateHolidayDate(2099, { easterOffset: 0 })).toEqual(new Date(2099, 3, 12)); + }); + + test("should apply a negative and a positive offset from Easter", () => { + expect(resolveStateHolidayDate(2024, { easterOffset: -47 })).toEqual(new Date(2024, 1, 13)); + expect(resolveStateHolidayDate(2024, { easterOffset: -2 })).toEqual(new Date(2024, 2, 29)); + expect(resolveStateHolidayDate(2024, { easterOffset: 60 })).toEqual(new Date(2024, 4, 30)); + expect(resolveStateHolidayDate(2023, { easterOffset: 60 })).toEqual(new Date(2023, 5, 8)); + }); + + test("should resolve a fixed day and month", () => { + expect(resolveStateHolidayDate(2024, { day: 9, month: 7 })).toEqual(new Date(2024, 6, 9)); + expect(resolveStateHolidayDate(1901, { day: 1, month: 1 })).toEqual(new Date(1901, 0, 1)); + }); + + test("should prefer the Easter offset when a rule carries both forms", () => { + expect(resolveStateHolidayDate(2024, { day: 9, month: 7, easterOffset: 0 })).toEqual( + new Date(2024, 2, 31), + ); + }); + + test("should throw when the rule defines neither an Easter offset nor both day and month", () => { + const message = + "State holiday entry must define either `easterOffset` or both `day` and `month`"; + + expect(() => resolveStateHolidayDate(2024, {})).toThrow(message); + expect(() => resolveStateHolidayDate(2024, { day: 10 })).toThrow(message); + expect(() => resolveStateHolidayDate(2024, { month: 5 })).toThrow(message); + }); +}); diff --git a/src/_internals/resolve-state-holiday-date/resolve-state-holiday-date.ts b/src/_internals/resolve-state-holiday-date/resolve-state-holiday-date.ts new file mode 100644 index 00000000..c1398b3a --- /dev/null +++ b/src/_internals/resolve-state-holiday-date/resolve-state-holiday-date.ts @@ -0,0 +1,71 @@ +/** How a holiday's date is defined: a fixed day and month, or an offset in days from Easter Sunday. */ +export type HolidayDateRule = { + /** Day of the month, 1 to 31, used together with `month`. */ + day?: number; + /** Month, 1 to 12, used together with `day`. */ + month?: number; + /** Offset in days from Easter Sunday (Carnaval is -47, Corpus Christi is 60); Easter itself is 0. */ + easterOffset?: number; +}; + +function calculateEaster(year: number): Date { + const a = year % 19; + const b = Math.floor(year / 100); + const c = year % 100; + const d = Math.floor(b / 4); + const e = b % 4; + const f = Math.floor((b + 8) / 25); + const g = Math.floor((b - f + 1) / 3); + const h = (19 * a + b - d - g + 15) % 30; + const i = Math.floor(c / 4); + const k = c % 4; + const l = (32 + 2 * e + 2 * i - h - k) % 7; + const m = Math.floor((a + 11 * h + 22 * l) / 451); + + const month = Math.floor((h + l - 7 * m + 114) / 31) - 1; + const day = ((h + l - 7 * m + 114) % 31) + 1; + + return new Date(year, month, day); +} + +function calculateHolidayFromEaster(year: number, offset: number): Date { + const easterDate = calculateEaster(year); + const holidayDate = new Date(easterDate); + holidayDate.setDate(easterDate.getDate() + offset); + return holidayDate; +} + +/** + * Resolves the date of a holiday in a given year: a fixed `day`/`month` pair, or an offset in + * days from Easter Sunday, computed with the Meeus/Jones/Butcher algorithm. + * + * @param {number} year - The four digit year. + * @param {HolidayDateRule} rule - The fixed date or the Easter offset of the holiday. + * @returns {Date} The holiday date in the local time zone. + * @throws {Error} When the rule defines neither `easterOffset` nor both `day` and `month`. + * + * @example + * ```typescript + * resolveStateHolidayDate(2024, { easterOffset: 0 }); // 2024-03-31 (Easter Sunday) + * resolveStateHolidayDate(2024, { easterOffset: 60 }); // 2024-05-30 (Corpus Christi) + * resolveStateHolidayDate(2024, { day: 9, month: 7 }); // 2024-07-09 + * ``` + * + * @see Based on: https://en.wikipedia.org/wiki/Date_of_Easter#Anonymous_Gregorian_algorithm + */ +export const resolveStateHolidayDate = ( + year: number, + { day, month, easterOffset }: HolidayDateRule, +): Date => { + if (easterOffset !== undefined) { + return calculateHolidayFromEaster(year, easterOffset); + } + + if (day !== undefined && month !== undefined) { + return new Date(year, month - 1, day); + } + + throw new Error( + "State holiday entry must define either `easterOffset` or both `day` and `month`", + ); +}; diff --git a/src/get-holidays/get-holidays.test.ts b/src/get-holidays/get-holidays.test.ts index c75be584..f125e0ce 100644 --- a/src/get-holidays/get-holidays.test.ts +++ b/src/get-holidays/get-holidays.test.ts @@ -408,39 +408,6 @@ describe("getHolidays", () => { expect(rjHolidays.some((h) => h.name === "São Sebastião")).toBe(false); }); - test("should throw when a state holiday entry defines neither easterOffset nor both day and month", () => { - const entries = STATE_HOLIDAYS.AC ?? []; - entries.push({ name: "Feriado estadual sem data" }); - - try { - expect(() => getHolidays({ year: 2098, stateCode: "AC" })).toThrow( - "State holiday entry must define either `easterOffset` or both `day` and `month`", - ); - } finally { - entries.pop(); - } - }); - - test("should throw when a state holiday entry defines only one of day/month, without easterOffset", () => { - const incompleteEntries = [ - { name: "Feriado com apenas o mês", month: 5 }, - { name: "Feriado com apenas o dia", day: 10 }, - ]; - - for (const entry of incompleteEntries) { - const entries = STATE_HOLIDAYS.AC ?? []; - entries.push(entry); - - try { - expect(() => getHolidays({ year: 2097, stateCode: "AC" })).toThrow( - "State holiday entry must define either `easterOffset` or both `day` and `month`", - ); - } finally { - entries.pop(); - } - } - }); - describe("properties", () => { const yearArbitrary = fc.integer({ min: HOLIDAYS_MIN_YEAR, max: HOLIDAYS_MAX_YEAR }); const stateCodeArbitrary = fc.constantFrom(...STATES.map((state) => state.code)); diff --git a/src/get-holidays/get-holidays.ts b/src/get-holidays/get-holidays.ts index 81b20d0e..32599866 100644 --- a/src/get-holidays/get-holidays.ts +++ b/src/get-holidays/get-holidays.ts @@ -1,11 +1,11 @@ import { HOLIDAYS_MAX_YEAR, HOLIDAYS_MIN_YEAR } from "../_internals/constants/holidays"; import { type StateCode } from "../_internals/constants/states"; import { isNullish } from "../_internals/is-nullish/is-nullish"; +import { resolveStateHolidayDate } from "../_internals/resolve-state-holiday-date/resolve-state-holiday-date"; import { CONSCIENCIA_NEGRA_HOLIDAY_NAME, CONSCIENCIA_NEGRA_NATIONAL_SINCE_YEAR, FIXED_HOLIDAYS, - type StateHolidayEntry, STATE_HOLIDAYS, } from "./constants"; @@ -30,50 +30,6 @@ export type GetHolidaysOptions = { stateCode?: StateCode; }; -function calculateEaster(year: number): Date { - const a = year % 19; - const b = Math.floor(year / 100); - const c = year % 100; - const d = Math.floor(b / 4); - const e = b % 4; - const f = Math.floor((b + 8) / 25); - const g = Math.floor((b - f + 1) / 3); - const h = (19 * a + b - d - g + 15) % 30; - const i = Math.floor(c / 4); - const k = c % 4; - const l = (32 + 2 * e + 2 * i - h - k) % 7; - const m = Math.floor((a + 11 * h + 22 * l) / 451); - - const month = Math.floor((h + l - 7 * m + 114) / 31) - 1; - const day = ((h + l - 7 * m + 114) % 31) + 1; - - return new Date(year, month, day); -} - -function calculateHolidayFromEaster(year: number, offset: number): Date { - const easterDate = calculateEaster(year); - const holidayDate = new Date(easterDate); - holidayDate.setDate(easterDate.getDate() + offset); - return holidayDate; -} - -function resolveStateHolidayDate( - year: number, - { day, month, easterOffset }: Pick, -): Date { - if (easterOffset !== undefined) { - return calculateHolidayFromEaster(year, easterOffset); - } - - if (day !== undefined && month !== undefined) { - return new Date(year, month - 1, day); - } - - throw new Error( - "State holiday entry must define either `easterOffset` or both `day` and `month`", - ); -} - let cache: Map | undefined; const cloneHolidays = (holidays: Holiday[]): Holiday[] => @@ -98,17 +54,17 @@ const computeHolidays = (year: number, stateCode: StateCode | undefined): Holida }); } - const easterDate = calculateEaster(year); + const easterDate = resolveStateHolidayDate(year, { easterOffset: 0 }); holidays.push( { name: "Carnaval (terça-feira)", - date: calculateHolidayFromEaster(year, -47), + date: resolveStateHolidayDate(year, { easterOffset: -47 }), type: "optional", }, { name: "Sexta-feira Santa", - date: calculateHolidayFromEaster(year, -2), + date: resolveStateHolidayDate(year, { easterOffset: -2 }), type: "national", }, { @@ -118,7 +74,7 @@ const computeHolidays = (year: number, stateCode: StateCode | undefined): Holida }, { name: "Corpus Christi", - date: calculateHolidayFromEaster(year, 60), + date: resolveStateHolidayDate(year, { easterOffset: 60 }), type: "optional", }, ); From cdf87292fc7949c024e14d60d5a4d17c44a0e5d7 Mon Sep 17 00:00:00 2001 From: Hyan Mandian <5044101+hyanmandian@users.noreply.github.com> Date: Sat, 12 Sep 2026 13:38:43 -0300 Subject: [PATCH 04/11] ci(tree-shaking): weigh the same export set on both sides and tell measurement errors apart The full-import gate compared the head bundle of the exports that survived against the base bundle of every base export, so removing a large export hid growth elsewhere. The base run now receives the head snapshot (--surviving) and also measures the bundle of the exports both sides share, and the comparison uses that number. The script exits with 2 when the comparison itself cannot run (invalid snapshot, measurement failure) and with 1 for a regression; the workflow fails on 2 regardless of the tree-shaking: accepted label, which only ever covered regressions. --- .github/workflows/build.yml | 18 ++++++++-- scripts/tree-shaking.ts | 66 ++++++++++++++++++++++++++++--------- 2 files changed, 66 insertions(+), 18 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index e4014ea2..cbcbedc6 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -70,7 +70,8 @@ jobs: id: base run: | if [ -f base/scripts/tree-shaking.ts ]; then - (cd base && npm ci && npm run build && node scripts/tree-shaking.ts --json ../base.json) || true + node scripts/tree-shaking.ts --json head.json + (cd base && npm ci && npm run build && node scripts/tree-shaking.ts --json ../base.json --surviving ../head.json) || true fi if [ -f base.json ]; then echo "measured=true" >> "$GITHUB_OUTPUT" @@ -84,7 +85,12 @@ jobs: continue-on-error: true run: | if [ "${{ steps.base.outputs.measured }}" = "true" ]; then + set +e node scripts/tree-shaking.ts --compare base.json --markdown tree-shaking.md + code=$? + set -e + echo "code=$code" >> "$GITHUB_OUTPUT" + exit "$code" else { echo "## Tree-shaking report" @@ -104,7 +110,9 @@ jobs: printf '## Tree-shaking report\n\nThe measurement step failed before producing a report. See the job log.\n' > tree-shaking.md fi sed -i '1i' tree-shaking.md - if [ "${{ steps.compare.outcome }}" = "failure" ]; then + if [ "${{ steps.compare.outputs.code }}" = "2" ]; then + printf '\n> The comparison itself failed (see the job log); the "tree-shaking: accepted" label does not cover this.\n' >> tree-shaking.md + elif [ "${{ steps.compare.outcome }}" = "failure" ]; then if [ "${{ contains(github.event.pull_request.labels.*.name, 'tree-shaking: accepted') }}" = "true" ]; then printf '\n> Regression accepted through the "tree-shaking: accepted" label.\n' >> tree-shaking.md else @@ -135,7 +143,11 @@ jobs: run: cat tree-shaking.md >> "$GITHUB_STEP_SUMMARY" - name: Fail on regression - if: "${{ github.event_name == 'pull_request' && steps.compare.outcome == 'failure' && !contains(github.event.pull_request.labels.*.name, 'tree-shaking: accepted') }}" + if: "${{ github.event_name == 'pull_request' && steps.compare.outcome == 'failure' && steps.compare.outputs.code != '2' && !contains(github.event.pull_request.labels.*.name, 'tree-shaking: accepted') }}" + run: exit 1 + + - name: Fail when the comparison itself failed + if: "${{ github.event_name == 'pull_request' && steps.compare.outputs.code == '2' }}" run: exit 1 - name: Summarize tree-shaking diff --git a/scripts/tree-shaking.ts b/scripts/tree-shaking.ts index 82e89883..23b6907b 100644 --- a/scripts/tree-shaking.ts +++ b/scripts/tree-shaking.ts @@ -12,12 +12,19 @@ * node scripts/tree-shaking.ts --json * Also write `{ full: {bytes,gzip}, exports: { name: {bytes,gzip} } }` to ``. * + * node scripts/tree-shaking.ts --json --surviving + * Also measure the bundle that imports only the exports listed in `` (a + * snapshot of the other side of a comparison) and store it as `surviving`, so that the + * comparison below weighs the same export set on both sides. + * * node scripts/tree-shaking.ts --compare * Diff the current build against a snapshot produced by a previous `--json` run, print a * Markdown report, and fail (exit 1) when a pre-existing export grew by more than * REGRESSION_PERCENT_THRESHOLD and more than REGRESSION_BYTES_THRESHOLD, or when the * bundle importing every export that already existed on the base grew by more than - * FULL_IMPORT_PERCENT_THRESHOLD (new exports never count as a regression). + * FULL_IMPORT_PERCENT_THRESHOLD (new exports never count as a regression). Exit code 1 + * means a regression; exit code 2 means the comparison itself could not run (invalid + * snapshot, measurement failure). * * node scripts/tree-shaking.ts --markdown * With `--compare`, also write the Markdown report to `` (for posting as a PR comment). @@ -55,10 +62,12 @@ type Measurement = { type Snapshot = { full: Measurement; exports: Record; + surviving?: Measurement; }; type Args = { json: string | undefined; + surviving: string | undefined; compare: string | undefined; markdown: string | undefined; dist: string | undefined; @@ -84,9 +93,16 @@ type CompareResult = { }; const parseArgs = (argv: string[]): Args => { - const args: Args = { json: undefined, compare: undefined, markdown: undefined, dist: undefined }; + const args: Args = { + json: undefined, + surviving: undefined, + compare: undefined, + markdown: undefined, + dist: undefined, + }; for (let i = 0; i < argv.length; i++) { if (argv[i] === "--json") args.json = argv[++i]; + else if (argv[i] === "--surviving") args.surviving = argv[++i]; else if (argv[i] === "--compare") args.compare = argv[++i]; else if (argv[i] === "--markdown") args.markdown = argv[++i]; else if (argv[i] === "--dist") args.dist = argv[++i]; @@ -316,8 +332,9 @@ const compareSnapshots = (base: Snapshot, head: Snapshot, existing: Measurement) changed.sort((a, b) => Math.abs(b.deltaBytes) - Math.abs(a.deltaBytes)); - const fullDeltaBytes = existing.bytes - base.full.bytes; - const fullDeltaPercent = base.full.bytes === 0 ? 0 : fullDeltaBytes / base.full.bytes; + const baseExisting = base.surviving ?? base.full; + const fullDeltaBytes = existing.bytes - baseExisting.bytes; + const fullDeltaPercent = baseExisting.bytes === 0 ? 0 : fullDeltaBytes / baseExisting.bytes; const regressions = changed.filter( (row) => @@ -418,7 +435,7 @@ const renderMarkdown = ( "", "| | Base | Head | Δ |", "| --- | ---: | ---: | ---: |", - `| Pre-existing exports, all imported | ${formatBytes(base.full.bytes)} | ${formatBytes(existing.bytes)} (gzip ${formatBytes(existing.gzip)}) | ${result.fullImportRegressed ? "🔴 " : ""}${formatDelta(result.fullDeltaBytes, result.fullDeltaPercent)} |`, + `| Pre-existing exports, all imported | ${formatBytes((base.surviving ?? base.full).bytes)} | ${formatBytes(existing.bytes)} (gzip ${formatBytes(existing.gzip)}) | ${result.fullImportRegressed ? "🔴 " : ""}${formatDelta(result.fullDeltaBytes, result.fullDeltaPercent)} |`, `| Full import | ${formatBytes(base.full.bytes)} | ${formatBytes(head.full.bytes)} (gzip ${formatBytes(head.full.gzip)}) | ${formatDelta(head.full.bytes - base.full.bytes, base.full.bytes === 0 ? 0 : (head.full.bytes - base.full.bytes) / base.full.bytes)} |`, `| Exports | ${Object.keys(base.exports).length} | ${Object.keys(head.exports).length} | ${formatCount(Object.keys(head.exports).length - Object.keys(base.exports).length)} |`, "", @@ -477,8 +494,21 @@ const isSnapshot = (value: unknown): value is Snapshot => isMeasurement(value.full) && "exports" in value && isRecord(value.exports) && + (!("surviving" in value) || value.surviving === undefined || isMeasurement(value.surviving)) && Object.values(value.exports).every((entry) => isMeasurement(entry)); +const readSnapshot = async (path: string): Promise => { + const parsed: unknown = JSON.parse(await readFile(path, "utf8")); + + if (!isSnapshot(parsed)) { + throw new Error(`${path} is not a valid tree-shaking snapshot`); + } + + return parsed; +}; + +const COMPARISON_ERROR_EXIT_CODE = 2; + const main = async (): Promise => { const args = parseArgs(process.argv.slice(2)); const packageRoot = @@ -488,19 +518,20 @@ const main = async (): Promise => { if (args.json !== undefined && args.json !== "") { const snapshot: Snapshot = { full, exports: exportsMap }; + + if (args.surviving !== undefined && args.surviving !== "") { + const other = await readSnapshot(resolve(process.cwd(), args.surviving)); + const survivingNames = Object.keys(exportsMap) + .filter((name) => name in other.exports && !aliasOf.has(name)) + .sort(); + snapshot.surviving = await measureNames(survivingNames, resolveDir); + } + await writeFile(resolve(process.cwd(), args.json), `${JSON.stringify(snapshot, null, "\t")}\n`); } if (args.compare !== undefined && args.compare !== "") { - const comparePath = resolve(process.cwd(), args.compare); - const compareContents = await readFile(comparePath, "utf8"); - const parsedBase: unknown = JSON.parse(compareContents); - - if (!isSnapshot(parsedBase)) { - throw new Error(`${comparePath} is not a valid tree-shaking snapshot`); - } - - const base = parsedBase; + const base = await readSnapshot(resolve(process.cwd(), args.compare)); const head: Snapshot = { full, exports: exportsMap }; const existingNames = Object.keys(base.exports) .filter((name) => name in exportsMap && !aliasOf.has(name)) @@ -527,4 +558,9 @@ const main = async (): Promise => { printTable(full, exportsMap, aliasOf); }; -await main(); +try { + await main(); +} catch (error) { + console.error(error instanceof Error ? error.message : String(error)); + process.exit(COMPARISON_ERROR_EXIT_CODE); +} From e16aadf351b8976e839b5ac4e6f169b89dd91cc3 Mon Sep 17 00:00:00 2001 From: Hyan Mandian <5044101+hyanmandian@users.noreply.github.com> Date: Sat, 12 Sep 2026 13:38:43 -0300 Subject: [PATCH 05/11] ci(datasets): validate the refreshed datasets and detect new files before opening the PR The weekly dataset job now runs vp check and the test suite on the regenerated files before opening the pull request (a pull request opened with GITHUB_TOKEN does not trigger the other workflows), detects untracked files as changes (git status instead of git diff) and targets the default branch explicitly even when dispatched from another branch. --- .github/workflows/datasets.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/datasets.yml b/.github/workflows/datasets.yml index 9e28edee..16197652 100644 --- a/.github/workflows/datasets.yml +++ b/.github/workflows/datasets.yml @@ -28,10 +28,13 @@ jobs: - name: Rebuild datasets run: npm run build:data + - name: Validate the regenerated datasets + run: npm run check && npx vp test --run + - name: Check for changes id: diff run: | - if git diff --quiet; then + if [ -z "$(git status --porcelain --untracked-files=all)" ]; then echo "changed=false" >> "$GITHUB_OUTPUT" else echo "changed=true" >> "$GITHUB_OUTPUT" @@ -43,6 +46,7 @@ jobs: with: commit-message: "chore(data): update IBGE/CONCLA datasets" branch: chore/update-datasets + base: ${{ github.event.repository.default_branch }} title: "chore(data): update IBGE/CONCLA datasets" body: | Automated weekly refresh of the cities, states and legal-natures From 401837658138f2efe21d9d5a5c9e2422eadb9b54 Mon Sep 17 00:00:00 2001 From: Hyan Mandian <5044101+hyanmandian@users.noreply.github.com> Date: Sat, 12 Sep 2026 14:08:22 -0300 Subject: [PATCH 06/11] ci(tree-shaking): lead the report with the size impact and collapse the full export list --- CONTRIBUTING.md | 7 +- scripts/tree-shaking.ts | 188 ++++++++++++++++++++++++---------------- 2 files changed, 119 insertions(+), 76 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 68b506b8..c034676a 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -122,8 +122,11 @@ duplicating logic (e.g. `src/_internals/format/format.ts`, above for every function the package exports, by building a one-import consumer bundle per export with esbuild and printing its size. There is no committed budgets file: instead, the `tree-shaking` job in CI measures every export's single-import bundle size on the PR's base -branch and on the PR head, then comments a Markdown diff on the PR (sorted by absolute delta, -with new and removed exports called out and unchanged exports collapsed). The check fails the PR +branch and on the PR head, then comments a Markdown report on the PR that leads with the impact: +a single "no bundle size impact" line when every export is the same size, otherwise the bundle +totals plus a "What changed" table listing only the exports that grew, shrank, appeared or +disappeared (sorted by absolute delta); the full per-export list is always there, collapsed. The +check fails the PR when a pre-existing export grows by more than 20% and more than 256 bytes, or when a bundle importing every export that already existed on the base grows by more than 5% (new exports never count as a regression); those thresholds live as constants at the top of diff --git a/scripts/tree-shaking.ts b/scripts/tree-shaking.ts index 23b6907b..dc4cb946 100644 --- a/scripts/tree-shaking.ts +++ b/scripts/tree-shaking.ts @@ -370,41 +370,72 @@ const formatCount = (value: number): string => (value > 0 ? `+${value}` : String const MAX_VISIBLE_ROWS = 20; -const renderRows = (title: string, header: string[], rows: string[]): string[] => { +const renderTable = (header: string[], rows: string[]): string[] => [ + `| ${header.join(" | ")} |`, + `| ${header.map((_column, index) => (index === 0 ? "---" : "---:")).join(" | ")} |`, + ...rows, +]; + +const renderCollapsed = (title: string, header: string[], rows: string[]): string[] => { if (rows.length === 0) return []; - const table = [ - `| ${header.join(" | ")} |`, - `| ${header.map((_column, index) => (index === 0 ? "---" : "---:")).join(" | ")} |`, + return [ + `
${title}`, + "", + ...renderTable(header, rows), + "", + "
", + "", ]; +}; + +const renderRows = (title: string, header: string[], rows: string[]): string[] => { + if (rows.length === 0) return []; const visible = rows.slice(0, MAX_VISIBLE_ROWS); const hidden = rows.slice(MAX_VISIBLE_ROWS); - const lines = [`### ${title} (${rows.length})`, "", ...table, ...visible, ""]; + const lines = [`### ${title} (${rows.length})`, "", ...renderTable(header, visible), ""]; if (hidden.length > 0) { - lines.push( - `
Show the other ${hidden.length}`, - "", - ...table, - ...hidden, - "", - "
", - "", - ); + lines.push(...renderCollapsed(`Show the other ${hidden.length}`, header, hidden)); } return lines; }; -const renderCollapsed = (title: string, header: string[], rows: string[]): string[] => { - if (rows.length === 0) return []; +const EXPORT_COLUMNS = ["Export", "Base", "Head", "Δ", "gzip"]; + +const renderExportRow = ( + marker: string, + name: string, + base: Measurement | null, + head: Measurement | null, +): string => { + const baseSize = base === null ? "—" : formatBytes(base.bytes); + const headSize = head === null ? "—" : formatBytes(head.bytes); + const gzip = formatBytes((head ?? base)?.gzip ?? 0); + const delta = + base === null + ? "new" + : head === null + ? "removed" + : formatDelta( + head.bytes - base.bytes, + base.bytes === 0 ? 0 : (head.bytes - base.bytes) / base.bytes, + ); + return `| ${marker} \`${name}\` | ${baseSize} | ${headSize} | ${delta} | ${gzip} |`; +}; + +const changeMarker = (row: CompareRow, result: CompareResult): string => + result.regressions.includes(row) ? "🔴" : row.deltaBytes > 0 ? "🟡" : "🟢"; + +const describeCounts = (result: CompareResult): string => { + const grown = result.changed.filter((row) => row.deltaBytes > 0).length; + const shrunk = result.changed.length - grown; return [ - `
${title} (${rows.length})`, - "", - `| ${header.join(" | ")} |`, - `| ${header.map((_column, index) => (index === 0 ? "---" : "---:")).join(" | ")} |`, - ...rows, - "", - "
", - "", - ]; + grown > 0 ? `${grown} grew` : "", + shrunk > 0 ? `${shrunk} shrank` : "", + result.added.length > 0 ? `${result.added.length} new` : "", + result.removed.length > 0 ? `${result.removed.length} removed` : "", + ] + .filter((part) => part !== "") + .join(", "); }; const renderMarkdown = ( @@ -413,65 +444,74 @@ const renderMarkdown = ( existing: Measurement, result: CompareResult, ): string => { - const grown = result.changed.filter((row) => row.deltaBytes > 0).length; - const shrunk = result.changed.length - grown; + const measured = Object.keys(head.exports).length; const regressionCount = result.regressions.length + (result.fullImportRegressed ? 1 : 0); - const status = - regressionCount === 0 - ? "✅ **No size regression.**" - : `❌ **${regressionCount} size regression${regressionCount === 1 ? "" : "s"}.**`; - const counts = [ - `${Object.keys(head.exports).length} exports measured`, - grown > 0 ? `${grown} grew` : "", - shrunk > 0 ? `${shrunk} shrank` : "", - result.added.length > 0 ? `${result.added.length} new` : "", - result.removed.length > 0 ? `${result.removed.length} removed` : "", - ].filter((part) => part !== ""); + const noImpact = + result.changed.length === 0 && + result.added.length === 0 && + result.removed.length === 0 && + result.fullDeltaBytes === 0; - const lines: string[] = [ - "## Tree-shaking report", - "", - `${status} ${counts.join(", ")}.`, - "", - "| | Base | Head | Δ |", - "| --- | ---: | ---: | ---: |", - `| Pre-existing exports, all imported | ${formatBytes((base.surviving ?? base.full).bytes)} | ${formatBytes(existing.bytes)} (gzip ${formatBytes(existing.gzip)}) | ${result.fullImportRegressed ? "🔴 " : ""}${formatDelta(result.fullDeltaBytes, result.fullDeltaPercent)} |`, - `| Full import | ${formatBytes(base.full.bytes)} | ${formatBytes(head.full.bytes)} (gzip ${formatBytes(head.full.gzip)}) | ${formatDelta(head.full.bytes - base.full.bytes, base.full.bytes === 0 ? 0 : (head.full.bytes - base.full.bytes) / base.full.bytes)} |`, - `| Exports | ${Object.keys(base.exports).length} | ${Object.keys(head.exports).length} | ${formatCount(Object.keys(head.exports).length - Object.keys(base.exports).length)} |`, - "", - ]; + const lines: string[] = ["## Tree-shaking report", ""]; - const changedRows = result.changed.map((row) => { - const marker = result.regressions.includes(row) ? "🔴" : row.deltaBytes > 0 ? "🟡" : "🟢"; - return `| ${marker} \`${row.name}\` | ${formatBytes(row.base.bytes)} | ${formatBytes(row.head.bytes)} | ${formatDelta(row.deltaBytes, row.deltaPercent)} | ${formatBytes(row.head.gzip)} |`; - }); + if (noImpact) { + lines.push( + `✅ **No bundle size impact.** All ${measured} exports are the same size as on the base branch (full import ${formatBytes(head.full.bytes)}, gzip ${formatBytes(head.full.gzip)}).`, + "", + ); + } else { + const status = + regressionCount === 0 + ? "✅ **No size regression.**" + : `❌ **${regressionCount} size regression${regressionCount === 1 ? "" : "s"}.**`; + const counts = describeCounts(result); + lines.push( + `${status} ${counts === "" ? `${measured} exports measured` : `${counts} out of ${measured} exports`}.`, + "", + "| | Base | Head | Δ |", + "| --- | ---: | ---: | ---: |", + `| Pre-existing exports, all imported | ${formatBytes((base.surviving ?? base.full).bytes)} | ${formatBytes(existing.bytes)} (gzip ${formatBytes(existing.gzip)}) | ${result.fullImportRegressed ? "🔴 " : ""}${formatDelta(result.fullDeltaBytes, result.fullDeltaPercent)} |`, + `| Full import | ${formatBytes(base.full.bytes)} | ${formatBytes(head.full.bytes)} (gzip ${formatBytes(head.full.gzip)}) | ${formatDelta(head.full.bytes - base.full.bytes, base.full.bytes === 0 ? 0 : (head.full.bytes - base.full.bytes) / base.full.bytes)} |`, + `| Exports | ${Object.keys(base.exports).length} | ${measured} | ${formatCount(measured - Object.keys(base.exports).length)} |`, + "", + ...renderRows("What changed", EXPORT_COLUMNS, [ + ...result.changed.map((row) => + renderExportRow(changeMarker(row, result), row.name, row.base, row.head), + ), + ...result.added.map((item) => renderExportRow("🆕", item.name, null, item)), + ...result.removed.map((item) => renderExportRow("🗑️", item.name, item, null)), + ]), + ); + } + + const everyName = [ + ...new Set([...Object.keys(base.exports), ...Object.keys(head.exports)]), + ].sort(); + const changedByName = new Map(result.changed.map((row) => [row.name, row])); lines.push( - ...renderRows("Changed exports", ["Export", "Base", "Head", "Δ", "gzip"], changedRows), - ...renderCollapsed( - "New exports", - ["Export", "Size", "gzip"], - result.added.map( - (item) => `| \`${item.name}\` | ${formatBytes(item.bytes)} | ${formatBytes(item.gzip)} |`, - ), - ), - ...renderCollapsed( - "Removed exports", - ["Export", "Was"], - result.removed.map((item) => `| \`${item.name}\` | ${formatBytes(item.bytes)} |`), - ), ...renderCollapsed( - "Unchanged exports", - ["Export", "Size", "gzip"], - result.unchanged.map( - (row) => - `| \`${row.name}\` | ${formatBytes(row.head.bytes)} | ${formatBytes(row.head.gzip)} |`, - ), + `All exports (${everyName.length})`, + EXPORT_COLUMNS, + everyName.map((name) => { + const row = changedByName.get(name); + const baseMeasurement: Measurement | undefined = base.exports[name]; + const headMeasurement: Measurement | undefined = head.exports[name]; + const marker = + row === undefined + ? baseMeasurement === undefined + ? "🆕" + : headMeasurement === undefined + ? "🗑️" + : "⚪" + : changeMarker(row, result); + return renderExportRow(marker, name, baseMeasurement ?? null, headMeasurement ?? null); + }), ), "
How this is measured", "", "Every export is imported alone into an esbuild consumer bundle (minified, tree-shaken) built from the head and from the base of this pull request; the sizes are the resulting bundles, gzip is their gzipped size. " + `🔴 marks a regression: a pre-existing export that grew more than ${REGRESSION_PERCENT_THRESHOLD * 100}% and more than ${REGRESSION_BYTES_THRESHOLD} B, or the bundle importing every pre-existing export growing more than ${FULL_IMPORT_PERCENT_THRESHOLD * 100}%. ` + - "🟡 is growth under the threshold and 🟢 is a decrease. New exports never count as a regression. An intentional increase is accepted with the `tree-shaking: accepted` label.", + "🟡 is growth under the threshold, 🟢 a decrease, ⚪ no change, 🆕 an export that does not exist on the base (never a regression), 🗑️ an export that was removed. An intentional increase is accepted with the `tree-shaking: accepted` label.", "", "
", ); From 5981dd3b8da2c4264f3ab1b2e8ae9729f1551e15 Mon Sep 17 00:00:00 2001 From: Hyan Mandian <5044101+hyanmandian@users.noreply.github.com> Date: Sat, 12 Sep 2026 14:48:15 -0300 Subject: [PATCH 07/11] ci(lint): run the SonarJS rules through the oxlint JS plugin and fix what they flag --- CONTRIBUTING.md | 14 + package-lock.json | 1158 ++++++++++++++++- package.json | 1 + scripts/banks.ts | 2 +- scripts/tree-shaking.ts | 83 +- .../number-to-words/number-to-words.ts | 31 +- src/_internals/test/runtime.ts | 13 +- .../convert-date-to-words.ts | 10 +- src/generate-cnh/generate-cnh.test.ts | 6 +- src/generate-cnpj/generate-cnpj.test.ts | 6 +- src/generate-cpf/generate-cpf.test.ts | 6 +- src/generate-pis/generate-pis.test.ts | 6 +- .../get-address-info-by-cep.test.ts | 15 +- .../get-cep-info-by-address.test.ts | 26 +- src/is-valid-ie/is-valid-ie.ts | 1 - src/parse-pix-key/constants.ts | 2 +- src/parse-pix-payload/parse-pix-payload.ts | 18 +- vite.config.ts | 47 + 18 files changed, 1337 insertions(+), 108 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index c034676a..adac928f 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -154,6 +154,20 @@ check-digit code and the `null`-returning API on purpose. Test files relax the r make sense for production code (return types, JSDoc, the `unsafe-*` family, since the multi-runtime `expect` shim is untyped) and every `@ts-expect-error` must carry a description. +[SonarJS](https://github.com/SonarSource/SonarJS) runs as an +oxlint JS plugin (`lint.jsPlugins` in `vite.config.ts`) with every rule as an error, minus a +short list that is off on purpose right below the spread: formatting and naming rules that +`vp fmt` owns, the complexity/duplication rules already gated by `eslint/complexity` and jscpd, +`no-reference-error` (it reports TypeScript utility types), `max-union-size` and `pseudo-random` +(the 27 state codes and the generators' `Math.random` are intentional), `redundant-type-aliases` +(deprecated aliases kept for compatibility) and `todo-tag` (`test.todo` is a shim feature). It +adds what the Rust plugins do not have: cognitive complexity (25), regex complexity (25) and +regex bug patterns (anchor precedence, super-linear backtracking), nested ternaries and template +literals, redundant assignments and optional markers, and the test smells (hooks after test +cases, disabled or exclusive tests, assertions outside tests). Its type-aware rules are inert, +since oxlint does not hand ESLint plugins a type checker. The plugin adds about three seconds to +`vp check`. + `tsconfig.json` is `strict` plus `noImplicitOverride`, `noUnusedLocals`, `noUnusedParameters` and `noPropertyAccessFromIndexSignature`. `noUncheckedIndexedAccess` and `exactOptionalPropertyTypes` stay off on purpose: the lookup tables are indexed by digits the code has already validated, so diff --git a/package-lock.json b/package-lock.json index 2ab2069a..c2625797 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18,6 +18,7 @@ "@vitest/browser-webdriverio": "4.1.11", "@vitest/coverage-v8": "4.1.11", "esbuild": "0.27.2", + "eslint-plugin-sonarjs": "^4.2.0", "fast-check": "4.10.0", "jscpd": "5.2.0", "knip": "6.35.1", @@ -1197,6 +1198,32 @@ "dev": true, "license": "ISC" }, + "node_modules/@cacheable/memory": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@cacheable/memory/-/memory-2.2.0.tgz", + "integrity": "sha512-CTLKqLItRCEixEAewD3/j9DB3/o96gpTPD4eJ1v+DGOlxZRZncRQkGYqqnAGCscYd6RNeXfGeiuCphsPtqyIfQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@cacheable/utils": "^2.5.0", + "@keyv/bigmap": "^1.3.1", + "hookified": "^1.15.1", + "keyv": "^5.6.0" + } + }, + "node_modules/@cacheable/utils": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@cacheable/utils/-/utils-2.5.0.tgz", + "integrity": "sha512-buipgOVDkkPXNR5+xBpDw7Zk2n1EvU7qBJCNUcL7rhQ//kfpOXPAvQ511Os0vpLYJ1pZnvudNytkQt2hst3wqA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "hashery": "^1.5.1", + "keyv": "^5.6.0" + } + }, "node_modules/@commitlint/cli": { "version": "21.2.2", "resolved": "https://registry.npmjs.org/@commitlint/cli/-/cli-21.2.2.tgz", @@ -2095,6 +2122,233 @@ "node": ">=18" } }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.10.1", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.10.1.tgz", + "integrity": "sha512-cuadcxVFE8sDK6iWJbs8Sn0av2Nrh2QSGQhVlBW9AaAHqHwjWsZHT8LJ4hFGPh7ASBV2deFdM7H/DPjulmh8rg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", + "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/config-array": { + "version": "0.23.5", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.23.5.tgz", + "integrity": "sha512-Y3kKLvC1dvTOT+oGlqNQ1XLqK6D1HU2YXPc52NmAlJZbMMWDzGYXMiPRJ8TYD39muD/OTjlZmNJ4ib7dvSrMBA==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@eslint/object-schema": "^3.0.5", + "debug": "^4.3.1", + "minimatch": "^10.2.4" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + } + }, + "node_modules/@eslint/config-array/node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/@eslint/config-array/node_modules/brace-expansion": { + "version": "5.0.9", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.9.tgz", + "integrity": "sha512-ScQ4IuvIEF1TMlP7Zt+vjJ//9zlPb2SDcxWxM3bk8s6t6GGdJ7KO1dCcTidOPJKePW30LE/2cT7wCyPho9/Wxg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/@eslint/config-array/node_modules/minimatch": { + "version": "10.2.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.6.tgz", + "integrity": "sha512-vpLQEs+VLCr1nU0BXS07maYoFwlDAH0gngQuuttxIwutDFEMHq2blX+8vpgxDdK3J1PwjCJiep77OitTZ4Ll1A==", + "dev": true, + "license": "BlueOak-1.0.0", + "peer": true, + "dependencies": { + "brace-expansion": "^5.0.8" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@eslint/config-helpers": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.7.0.tgz", + "integrity": "sha512-DObd/KKUsU+FaFv4PLxSRenpXfQWmPXXP3pPZ6/K1PCrMu2vQpMDMuQe/BqYeoLcz8ro0bVDF1RxOJgfVEdhUw==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@eslint/core": "^1.2.1" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + } + }, + "node_modules/@eslint/core": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-1.2.1.tgz", + "integrity": "sha512-MwcE1P+AZ4C6DWlpin/OmOA54mmIZ/+xZuJiQd4SyB29oAJjN30UW9wkKNptW2ctp4cEsvhlLY/CsQ1uoHDloQ==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + } + }, + "node_modules/@eslint/object-schema": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-3.0.5.tgz", + "integrity": "sha512-vqTaUEgxzm+YDSdElad6PiRoX4t8VGDjCtt05zn4nU810UIx/uNEV7/lZJ6KwFThKZOzOxzXy48da+No7HZaMw==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + } + }, + "node_modules/@eslint/plugin-kit": { + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.7.3.tgz", + "integrity": "sha512-IkO+/KEUvwbVpiURZg+P7zF74z5Jxe0UgJxVni+RtoHQ6IZieXaO02kmadomap/q+l6bc/jdPGGqTjhuZnuz1Q==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@eslint/core": "^1.2.1", + "levn": "^0.4.1" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + } + }, + "node_modules/@humanfs/core": { + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.2.tgz", + "integrity": "sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@humanfs/types": "^0.15.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node": { + "version": "0.16.8", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.8.tgz", + "integrity": "sha512-gE1eQNZ3R++kTzFUpdGlpmy8kDZD/MLyHqDwqjkVQI0JMdI1D51sy1H958PNXYkM2rAac7e5/CnIKZrHtPh3BQ==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@humanfs/core": "^0.19.2", + "@humanfs/types": "^0.15.0", + "@humanwhocodes/retry": "^0.4.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/types": { + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/@humanfs/types/-/types-0.15.0.tgz", + "integrity": "sha512-ZZ1w0aoQkwuUuC7Yf+7sdeaNfqQiiLcSRbfI08oAxqLtpXQr9AIVX7Ay7HLDuiLYAaFPu8oBYNq/QIi9URHJ3Q==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/retry": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", + "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, "node_modules/@inquirer/ansi": { "version": "2.0.8", "resolved": "https://registry.npmjs.org/@inquirer/ansi/-/ansi-2.0.8.tgz", @@ -2513,6 +2767,32 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@keyv/bigmap": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@keyv/bigmap/-/bigmap-1.3.1.tgz", + "integrity": "sha512-WbzE9sdmQtKy8vrNPa9BRnwZh5UF4s1KTmSK0KUVLo3eff5BlQNNWDnFOouNpKfPKDnms9xynJjsMYjMaT/aFQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "hashery": "^1.4.0", + "hookified": "^1.15.0" + }, + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "keyv": "^5.6.0" + } + }, + "node_modules/@keyv/serialize": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@keyv/serialize/-/serialize-1.1.1.tgz", + "integrity": "sha512-dXn3FZhPv0US+7dtJsIi2R+c7qWYiReoEh5zUntWCf4oSpMNib8FDhSoed6m3QyZdx5hK7iLFkYk3rNxwt8vTA==", + "dev": true, + "license": "MIT", + "peer": true + }, "node_modules/@loaderkit/resolve": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/@loaderkit/resolve/-/resolve-1.0.6.tgz", @@ -5123,6 +5403,14 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/esrecurse": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@types/esrecurse/-/esrecurse-4.3.1.tgz", + "integrity": "sha512-xJBAbDifo5hpffDBuHl0Y8ywswbiAp/Wi7Y/GtAgSlZyIABppyurxVueOPE8LUQOxdlgi6Zqce7uoEpqNTeiUw==", + "dev": true, + "license": "MIT", + "peer": true + }, "node_modules/@types/estree": { "version": "1.0.9", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", @@ -5144,6 +5432,14 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true, + "license": "MIT", + "peer": true + }, "node_modules/@types/node": { "version": "20.19.43", "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.43.tgz", @@ -6108,6 +6404,31 @@ "node": ">=6.5" } }, + "node_modules/acorn": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.18.0.tgz", + "integrity": "sha512-lGq+9yr1/GuAWaVYIHRjvvySG5/4VfKIvC8EWxStPdcDh/Ka7FG3twP6v4d5BkravUilhIAsG4Qj83t02LWUPQ==", + "dev": true, + "license": "MIT", + "peer": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "license": "MIT", + "peer": true, + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, "node_modules/agent-base": { "version": "7.1.4", "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", @@ -6571,6 +6892,44 @@ "node": ">=8.0.0" } }, + "node_modules/builtin-modules": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-3.3.0.tgz", + "integrity": "sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/cacheable": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/cacheable/-/cacheable-2.5.0.tgz", + "integrity": "sha512-60cyAOytib/OzBw1JNSoSV/boK1AtHryDIjvVBk7XbN4ugfkM3+Sry7fEjNgPMGgOjuaZPAp8ruZ0Cxafwyq9g==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@cacheable/memory": "^2.2.0", + "@cacheable/utils": "^2.5.0", + "hookified": "^1.15.0", + "keyv": "^5.6.0", + "qified": "^0.10.1" + } + }, "node_modules/call-bind-apply-helpers": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", @@ -7116,6 +7475,14 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true, + "license": "MIT", + "peer": true + }, "node_modules/deepmerge-ts": { "version": "8.0.2", "resolved": "https://registry.npmjs.org/deepmerge-ts/-/deepmerge-ts-8.0.2.tgz", @@ -7537,6 +7904,20 @@ "node": ">=6" } }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/escodegen": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.1.0.tgz", @@ -7559,33 +7940,314 @@ "source-map": "~0.6.1" } }, - "node_modules/esprima": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "node_modules/eslint": { + "version": "10.10.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-10.10.0.tgz", + "integrity": "sha512-NPXn6r5zl4uET1DAVPaOwzX3rut4c0wcmw3dWJAfOsTM5+TogXo0DDjz8pwm/hL8cyVNpHqeK4JpN0NjnyFFNw==", "dev": true, - "license": "BSD-2-Clause", + "license": "MIT", + "peer": true, + "workspaces": [ + "packages/*" + ], + "dependencies": { + "@eslint-community/eslint-utils": "^4.8.0", + "@eslint-community/regexpp": "^4.12.2", + "@eslint/config-array": "^0.23.5", + "@eslint/config-helpers": "^0.7.0", + "@eslint/core": "^1.2.1", + "@eslint/plugin-kit": "^0.7.3", + "@humanfs/node": "^0.16.6", + "@humanwhocodes/module-importer": "^1.0.1", + "@humanwhocodes/retry": "^0.4.2", + "@types/estree": "^1.0.6", + "ajv": "^6.14.0", + "cross-spawn": "^7.0.6", + "debug": "^4.3.2", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^9.1.2", + "eslint-visitor-keys": "^5.0.1", + "espree": "^11.2.0", + "esquery": "^1.7.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "11.1.5 || >11.1.6 <12", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "minimatch": "^10.2.5", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3" + }, "bin": { - "esparse": "bin/esparse.js", - "esvalidate": "bin/esvalidate.js" + "eslint": "bin/eslint.js" }, "engines": { - "node": ">=4" + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "jiti": "*" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + } } }, - "node_modules/estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">=4.0" + "node_modules/eslint-plugin-sonarjs": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-sonarjs/-/eslint-plugin-sonarjs-4.2.0.tgz", + "integrity": "sha512-bqADfuNtTL7VK6RU29eoiFTtaaBKIpVPuX3bOl+rBpWSBa0zIBVZlqZNZQjfP6s4iXkAJokv5IsD8OsACkwApg==", + "dev": true, + "license": "LGPL-3.0-only", + "dependencies": { + "@eslint-community/regexpp": "^4.12.2", + "builtin-modules": "^3.3.0", + "bytes": "^3.1.2", + "functional-red-black-tree": "^1.0.1", + "globals": "^17.7.0", + "jsx-ast-utils-x": "^0.1.0", + "lodash.merge": "^4.6.2", + "minimatch": "^10.2.5", + "scslre": "^0.3.0", + "semver": "^7.8.5", + "ts-api-utils": "^2.5.0", + "typescript": ">=5 <6.1.0", + "yaml": "^2.9.0" + }, + "peerDependencies": { + "eslint": "^8.0.0 || ^9.0.0 || ^10.0.0" } }, - "node_modules/estree-walker": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "node_modules/eslint-plugin-sonarjs/node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/eslint-plugin-sonarjs/node_modules/brace-expansion": { + "version": "5.0.9", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.9.tgz", + "integrity": "sha512-ScQ4IuvIEF1TMlP7Zt+vjJ//9zlPb2SDcxWxM3bk8s6t6GGdJ7KO1dCcTidOPJKePW30LE/2cT7wCyPho9/Wxg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/eslint-plugin-sonarjs/node_modules/minimatch": { + "version": "10.2.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.6.tgz", + "integrity": "sha512-vpLQEs+VLCr1nU0BXS07maYoFwlDAH0gngQuuttxIwutDFEMHq2blX+8vpgxDdK3J1PwjCJiep77OitTZ4Ll1A==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.8" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/eslint-scope": { + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-9.1.2.tgz", + "integrity": "sha512-xS90H51cKw0jltxmvmHy2Iai1LIqrfbw57b79w/J7MfvDfkIkFZ+kj6zC3BjtUwh150HsSSdxXZcsuv72miDFQ==", + "dev": true, + "license": "BSD-2-Clause", + "peer": true, + "dependencies": { + "@types/esrecurse": "^4.3.1", + "@types/estree": "^1.0.8", + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz", + "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/ajv": { + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.15.0.tgz", + "integrity": "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/eslint/node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/eslint/node_modules/brace-expansion": { + "version": "5.0.9", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.9.tgz", + "integrity": "sha512-ScQ4IuvIEF1TMlP7Zt+vjJ//9zlPb2SDcxWxM3bk8s6t6GGdJ7KO1dCcTidOPJKePW30LE/2cT7wCyPho9/Wxg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/eslint/node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/eslint/node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/eslint/node_modules/minimatch": { + "version": "10.2.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.6.tgz", + "integrity": "sha512-vpLQEs+VLCr1nU0BXS07maYoFwlDAH0gngQuuttxIwutDFEMHq2blX+8vpgxDdK3J1PwjCJiep77OitTZ4Ll1A==", + "dev": true, + "license": "BlueOak-1.0.0", + "peer": true, + "dependencies": { + "brace-expansion": "^5.0.8" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/espree": { + "version": "11.2.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-11.2.0.tgz", + "integrity": "sha512-7p3DrVEIopW1B1avAGLuCSh1jubc01H2JHc8B4qqGblmg5gI9yumBgACjWo4JlIc04ufug4xJ3SQI8HkS/Rgzw==", + "dev": true, + "license": "BSD-2-Clause", + "peer": true, + "dependencies": { + "acorn": "^8.16.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^5.0.1" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true, + "license": "BSD-2-Clause", + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/esquery": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz", + "integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==", + "dev": true, + "license": "BSD-3-Clause", + "peer": true, + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "license": "BSD-2-Clause", + "peer": true, + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", "dev": true, "license": "MIT", @@ -7758,6 +8420,22 @@ "dev": true, "license": "MIT" }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true, + "license": "MIT", + "peer": true + }, "node_modules/fast-string-truncated-width": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/fast-string-truncated-width/-/fast-string-truncated-width-3.0.3.tgz", @@ -7904,6 +8582,56 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/file-entry-cache": { + "version": "11.1.5", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-11.1.5.tgz", + "integrity": "sha512-+PFTHITI08JIGhnNpGNI8T8inUpgZfk3GNEqfT9R2zZV2iFXg3CvqzSl/uEhs7TSGujYRELEANyDvS8Fj7+S7Q==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "flat-cache": "^6.1.23" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat-cache": { + "version": "6.1.23", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-6.1.23.tgz", + "integrity": "sha512-f++BY9pTk+983xK1FLzlLpmM0i0z+jHmx3QESGkURMXujQZz1k5wzwX6hjnQ8goaD0B+sYnDK1yZ6MTyZfUaqA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "cacheable": "^2.5.0", + "flatted": "^3.4.2", + "hookified": "^1.15.0" + } + }, + "node_modules/flatted": { + "version": "3.4.4", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.4.tgz", + "integrity": "sha512-5+ybhBZANEJxaH3X5evAFatUxLfEHSr7n6kYJ+1Qd0mUqr4eu9gIf6GDbWHf8RJijHrjjO8G+la14SlL2SeS1Q==", + "dev": true, + "license": "ISC", + "peer": true + }, "node_modules/foreground-child": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", @@ -7978,6 +8706,13 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/functional-red-black-tree": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", + "integrity": "sha512-dsKNQNdj6xA3T+QlADDA7mOSlX0qiMINjn0cgr+eGHGsbSHzTabcIogz2+p/iqP1Xs6EP/sS2SbqH+brGTbq0g==", + "dev": true, + "license": "MIT" + }, "node_modules/geckodriver": { "version": "6.1.1", "resolved": "https://registry.npmjs.org/geckodriver/-/geckodriver-6.1.1.tgz", @@ -8151,6 +8886,20 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "license": "ISC", + "peer": true, + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, "node_modules/global-directory": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/global-directory/-/global-directory-5.0.0.tgz", @@ -8167,6 +8916,19 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/globals": { + "version": "17.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-17.12.0.tgz", + "integrity": "sha512-cezEd/DTyyht9cvSSURyygXPfy04GtWO/5e6ZPvH7fCtjKz9PYOmuawphw1Ctd1f6C+5JypXfGD7ahNMXvevBA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/gopd": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", @@ -8217,6 +8979,20 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/hashery": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/hashery/-/hashery-1.5.1.tgz", + "integrity": "sha512-iZyKG96/JwPz1N55vj2Ie2vXbhu440zfUfJvSwEqEbeLluk7NnapfGqa7LH0mOsnDxTF85Mx8/dyR6HfqcbmbQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "hookified": "^1.15.0" + }, + "engines": { + "node": ">=20" + } + }, "node_modules/hasown": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz", @@ -8230,6 +9006,14 @@ "node": ">= 0.4" } }, + "node_modules/hookified": { + "version": "1.15.1", + "resolved": "https://registry.npmjs.org/hookified/-/hookified-1.15.1.tgz", + "integrity": "sha512-MvG/clsADq1GPM2KGo2nyfaWVyn9naPiXrqIe4jYjXNZQt238kWyOGrsyc/DmRAQ+Re6yeo6yX/yoNCG5KAEVg==", + "dev": true, + "license": "MIT", + "peer": true + }, "node_modules/html-escaper": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", @@ -8349,6 +9133,17 @@ ], "license": "BSD-3-Clause" }, + "node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 4" + } + }, "node_modules/immediate": { "version": "3.0.6", "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz", @@ -8404,6 +9199,17 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=0.8.19" + } + }, "node_modules/inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", @@ -8454,6 +9260,17 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/is-fullwidth-code-point": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", @@ -8464,6 +9281,20 @@ "node": ">=8" } }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/is-plain-obj": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz", @@ -8833,6 +9664,14 @@ "dev": true, "license": "MIT" }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true, + "license": "MIT", + "peer": true + }, "node_modules/json5": { "version": "2.2.3", "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", @@ -8859,6 +9698,16 @@ "graceful-fs": "^4.1.6" } }, + "node_modules/jsx-ast-utils-x": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/jsx-ast-utils-x/-/jsx-ast-utils-x-0.1.0.tgz", + "integrity": "sha512-eQQBjBnsVtGacsG9uJNB8qOr3yA8rga4wAaGG1qRcBzSIvfhERLrWxMAM1hp5fcS6Abo8M4+bUBTekYR0qTPQw==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, "node_modules/jszip": { "version": "3.10.1", "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.10.1.tgz", @@ -8905,6 +9754,17 @@ "safe-buffer": "~5.1.0" } }, + "node_modules/keyv": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-5.6.0.tgz", + "integrity": "sha512-CYDD3SOtsHtyXeEORYRx2qBtpDJFjRTGXUtmNEMGyzYOKj1TE3tycdlho7kA1Ufx9OYWZzg52QFBGALTirzDSw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@keyv/serialize": "^1.1.1" + } + }, "node_modules/knip": { "version": "6.35.1", "resolved": "https://registry.npmjs.org/knip/-/knip-6.35.1.tgz", @@ -8990,6 +9850,21 @@ "safe-buffer": "~5.1.0" } }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, "node_modules/lie": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz", @@ -9302,6 +10177,23 @@ "userhome": "1.0.1" } }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/lockfile-lint": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/lockfile-lint/-/lockfile-lint-5.0.1.tgz", @@ -9358,6 +10250,13 @@ "dev": true, "license": "MIT" }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true, + "license": "MIT" + }, "node_modules/lodash.zip": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/lodash.zip/-/lodash.zip-4.2.0.tgz", @@ -9597,6 +10496,14 @@ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" } }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true, + "license": "MIT", + "peer": true + }, "node_modules/netmask": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/netmask/-/netmask-2.1.1.tgz", @@ -9717,6 +10624,25 @@ "wrappy": "1" } }, + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, "node_modules/oxc-parser": { "version": "0.148.0", "resolved": "https://registry.npmjs.org/oxc-parser/-/oxc-parser-0.148.0.tgz", @@ -9914,6 +10840,40 @@ "@oxlint-tsgolint/win32-x64": "7.0.2001" } }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/pac-proxy-agent": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/pac-proxy-agent/-/pac-proxy-agent-7.2.0.tgz", @@ -10067,6 +11027,17 @@ "url": "https://github.com/fb55/entities?sponsor=1" } }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=8" + } + }, "node_modules/path-expression-matcher": { "version": "1.6.2", "resolved": "https://registry.npmjs.org/path-expression-matcher/-/path-expression-matcher-1.6.2.tgz", @@ -10197,6 +11168,17 @@ "node": "^10 || ^12 || >=14" } }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 0.8.0" + } + }, "node_modules/pretty-format": { "version": "27.5.1", "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz", @@ -10325,6 +11307,17 @@ "once": "^1.3.1" } }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=6" + } + }, "node_modules/pure-rand": { "version": "8.4.2", "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-8.4.2.tgz", @@ -10342,6 +11335,28 @@ ], "license": "MIT" }, + "node_modules/qified": { + "version": "0.10.1", + "resolved": "https://registry.npmjs.org/qified/-/qified-0.10.1.tgz", + "integrity": "sha512-+Owyggi9IxT1ePKGafcI87ubSmxol6smwJ+RAHDQlx9+9cPwFWDiKFFCPuWhr9ignlGpZ9vDQLw67N4dcTVFEA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "hookified": "^2.1.1" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/qified/node_modules/hookified": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/hookified/-/hookified-2.2.0.tgz", + "integrity": "sha512-p/LgFzRN5FeoD3DLS6bkUapeye6E4SI6yJs6KetENd18S+FBthqYq2amJUWpt5z0EQwwHemidjY5OqJGEKm5uA==", + "dev": true, + "license": "MIT", + "peer": true + }, "node_modules/qs": { "version": "6.16.0", "resolved": "https://registry.npmjs.org/qs/-/qs-6.16.0.tgz", @@ -10413,6 +11428,33 @@ "node": ">=10" } }, + "node_modules/refa": { + "version": "0.12.1", + "resolved": "https://registry.npmjs.org/refa/-/refa-0.12.1.tgz", + "integrity": "sha512-J8rn6v4DBb2nnFqkqwy6/NnTYMcgLA+sLr0iIO41qpv0n+ngb7ksag2tMRl0inb1bbO/esUwzW1vbJi7K0sI0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/regexpp": "^4.8.0" + }, + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/regexp-ast-analysis": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/regexp-ast-analysis/-/regexp-ast-analysis-0.7.1.tgz", + "integrity": "sha512-sZuz1dYW/ZsfG17WSAG7eS85r5a0dDsvg+7BiiYR5o6lKCAtUrEwdmRmaGF6rwVj3LcmAeYkOWKEPlbPzN3Y3A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/regexpp": "^4.8.0", + "refa": "^0.12.1" + }, + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, "node_modules/require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", @@ -10632,6 +11674,21 @@ "dev": true, "license": "MIT" }, + "node_modules/scslre": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/scslre/-/scslre-0.3.0.tgz", + "integrity": "sha512-3A6sD0WYP7+QrjbfNA2FN3FsOaGGFoekCVgTyypy53gPxhbkCIjtO6YWgdrfM+n/8sI8JeXZOIxsHjMTNxQ4nQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/regexpp": "^4.8.0", + "refa": "^0.12.0", + "regexp-ast-analysis": "^0.7.0" + }, + "engines": { + "node": "^14.0.0 || >=16.0.0" + } + }, "node_modules/semver": { "version": "7.8.5", "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", @@ -11256,6 +12313,19 @@ "tree-kill": "cli.js" } }, + "node_modules/ts-api-utils": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.5.0.tgz", + "integrity": "sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.12" + }, + "peerDependencies": { + "typescript": ">=4.8.4" + } + }, "node_modules/tslib": { "version": "2.8.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", @@ -11273,6 +12343,20 @@ "node": ">=0.6.11 <=0.7.0 || >=0.7.3" } }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, "node_modules/type-fest": { "version": "4.26.0", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.26.0.tgz", @@ -11415,6 +12499,17 @@ "browserslist": ">= 4.21.0" } }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "license": "BSD-2-Clause", + "peer": true, + "dependencies": { + "punycode": "^2.1.0" + } + }, "node_modules/urlpattern-polyfill": { "version": "10.1.0", "resolved": "https://registry.npmjs.org/urlpattern-polyfill/-/urlpattern-polyfill-10.1.0.tgz", @@ -11886,6 +12981,17 @@ "node": ">=8" } }, + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/wrap-ansi": { "version": "8.1.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", @@ -12143,6 +13249,20 @@ "node": "*" } }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/yoctocolors": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/yoctocolors/-/yoctocolors-2.2.0.tgz", diff --git a/package.json b/package.json index 716876d3..d0d32757 100644 --- a/package.json +++ b/package.json @@ -112,6 +112,7 @@ "@vitest/browser-webdriverio": "4.1.11", "@vitest/coverage-v8": "4.1.11", "esbuild": "0.27.2", + "eslint-plugin-sonarjs": "^4.2.0", "fast-check": "4.10.0", "jscpd": "5.2.0", "knip": "6.35.1", diff --git a/scripts/banks.ts b/scripts/banks.ts index 2cf99f51..31674705 100644 --- a/scripts/banks.ts +++ b/scripts/banks.ts @@ -80,7 +80,7 @@ const fetchFromBacen = async (): Promise => { for (const row of rows) { const fields = parseCsvLine(row); - const ispb = fields[0]; + const [ispb] = fields; const code = fields[2]; const name = fields[5]; diff --git a/scripts/tree-shaking.ts b/scripts/tree-shaking.ts index dc4cb946..623add7d 100644 --- a/scripts/tree-shaking.ts +++ b/scripts/tree-shaking.ts @@ -92,6 +92,14 @@ type CompareResult = { fullImportRegressed: boolean; }; +const OPTION_FLAGS = new Map([ + ["--json", "json"], + ["--surviving", "surviving"], + ["--compare", "compare"], + ["--markdown", "markdown"], + ["--dist", "dist"], +]); + const parseArgs = (argv: string[]): Args => { const args: Args = { json: undefined, @@ -101,11 +109,12 @@ const parseArgs = (argv: string[]): Args => { dist: undefined, }; for (let i = 0; i < argv.length; i++) { - if (argv[i] === "--json") args.json = argv[++i]; - else if (argv[i] === "--surviving") args.surviving = argv[++i]; - else if (argv[i] === "--compare") args.compare = argv[++i]; - else if (argv[i] === "--markdown") args.markdown = argv[++i]; - else if (argv[i] === "--dist") args.dist = argv[++i]; + const option = OPTION_FLAGS.get(argv[i]); + + if (option === undefined) continue; + + i += 1; + args[option] = argv[i]; } return args; }; @@ -119,7 +128,8 @@ const mapWithConcurrency = async ( let cursor = 0; const worker = async (): Promise => { - const index = cursor++; + const index = cursor; + cursor += 1; if (index >= items.length) return; @@ -361,10 +371,13 @@ const formatPercent = (value: number): string => const formatBytes = (bytes: number): string => Math.abs(bytes) < 1024 ? `${bytes} B` : `${(bytes / 1024).toFixed(1)} KB`; -const formatDelta = (bytes: number, percent: number): string => - bytes === 0 - ? "0 B" - : `${bytes > 0 ? "+" : "-"}${formatBytes(Math.abs(bytes))} (${formatPercent(percent)})`; +const formatDelta = (bytes: number, percent: number): string => { + if (bytes === 0) return "0 B"; + const sign = bytes > 0 ? "+" : "-"; + return `${sign}${formatBytes(Math.abs(bytes))} (${formatPercent(percent)})`; +}; + +const plural = (count: number, word: string): string => `${count} ${word}${count === 1 ? "" : "s"}`; const formatCount = (value: number): string => (value > 0 ? `+${value}` : String(value)); @@ -401,6 +414,13 @@ const renderRows = (title: string, header: string[], rows: string[]): string[] = const EXPORT_COLUMNS = ["Export", "Base", "Head", "Δ", "gzip"]; +const formatRowDelta = (base: Measurement | null, head: Measurement | null): string => { + if (base === null) return "new"; + if (head === null) return "removed"; + const deltaBytes = head.bytes - base.bytes; + return formatDelta(deltaBytes, base.bytes === 0 ? 0 : deltaBytes / base.bytes); +}; + const renderExportRow = ( marker: string, name: string, @@ -410,20 +430,24 @@ const renderExportRow = ( const baseSize = base === null ? "—" : formatBytes(base.bytes); const headSize = head === null ? "—" : formatBytes(head.bytes); const gzip = formatBytes((head ?? base)?.gzip ?? 0); - const delta = - base === null - ? "new" - : head === null - ? "removed" - : formatDelta( - head.bytes - base.bytes, - base.bytes === 0 ? 0 : (head.bytes - base.bytes) / base.bytes, - ); - return `| ${marker} \`${name}\` | ${baseSize} | ${headSize} | ${delta} | ${gzip} |`; + return `| ${marker} \`${name}\` | ${baseSize} | ${headSize} | ${formatRowDelta(base, head)} | ${gzip} |`; +}; + +const changeMarker = (row: CompareRow, result: CompareResult): string => { + if (result.regressions.includes(row)) return "🔴"; + return row.deltaBytes > 0 ? "🟡" : "🟢"; }; -const changeMarker = (row: CompareRow, result: CompareResult): string => - result.regressions.includes(row) ? "🔴" : row.deltaBytes > 0 ? "🟡" : "🟢"; +const allExportsMarker = ( + row: CompareRow | undefined, + base: Measurement | undefined, + head: Measurement | undefined, + result: CompareResult, +): string => { + if (row !== undefined) return changeMarker(row, result); + if (base === undefined) return "🆕"; + return head === undefined ? "🗑️" : "⚪"; +}; const describeCounts = (result: CompareResult): string => { const grown = result.changed.filter((row) => row.deltaBytes > 0).length; @@ -463,10 +487,12 @@ const renderMarkdown = ( const status = regressionCount === 0 ? "✅ **No size regression.**" - : `❌ **${regressionCount} size regression${regressionCount === 1 ? "" : "s"}.**`; + : `❌ **${plural(regressionCount, "size regression")}.**`; const counts = describeCounts(result); + const scope = + counts === "" ? `${measured} exports measured` : `${counts} out of ${measured} exports`; lines.push( - `${status} ${counts === "" ? `${measured} exports measured` : `${counts} out of ${measured} exports`}.`, + `${status} ${scope}.`, "", "| | Base | Head | Δ |", "| --- | ---: | ---: | ---: |", @@ -496,14 +522,7 @@ const renderMarkdown = ( const row = changedByName.get(name); const baseMeasurement: Measurement | undefined = base.exports[name]; const headMeasurement: Measurement | undefined = head.exports[name]; - const marker = - row === undefined - ? baseMeasurement === undefined - ? "🆕" - : headMeasurement === undefined - ? "🗑️" - : "⚪" - : changeMarker(row, result); + const marker = allExportsMarker(row, baseMeasurement, headMeasurement, result); return renderExportRow(marker, name, baseMeasurement ?? null, headMeasurement ?? null); }), ), diff --git a/src/_internals/number-to-words/number-to-words.ts b/src/_internals/number-to-words/number-to-words.ts index d92e2e5e..94935cb3 100644 --- a/src/_internals/number-to-words/number-to-words.ts +++ b/src/_internals/number-to-words/number-to-words.ts @@ -45,11 +45,8 @@ const groupToWords = (value: number, gender?: NumberToWordsGender): string => { const segments: string[] = []; if (hundredsDigit > 0) { - segments.push( - value === 100 - ? HUNDRED_EXACT - : (gender === "feminine" ? HUNDREDS_FEMININE : HUNDREDS_MASCULINE)[hundredsDigit], - ); + const hundreds = gender === "feminine" ? HUNDREDS_FEMININE : HUNDREDS_MASCULINE; + segments.push(value === 100 ? HUNDRED_EXACT : hundreds[hundredsDigit]); } if (remainder > 0) { @@ -67,6 +64,19 @@ const groupToWords = (value: number, gender?: NumberToWordsGender): string => { return segments.join(" e "); }; +const scaledGroupToWords = ( + groupValue: number, + scale: number, + gender: NumberToWordsGender | undefined, +): string => { + const scaleWord = SCALE_WORDS[scale]; + + if (scale === 0) return groupToWords(groupValue, gender); + if (scale === 1 && groupValue === 1) return scaleWord.singular; + + return `${groupToWords(groupValue, gender)} ${groupValue === 1 ? scaleWord.singular : scaleWord.plural}`; +}; + const isRoundHundred = (value: number): boolean => value % 100 === 0; /** @@ -127,16 +137,7 @@ export const numberToWords = (value: number, options?: NumberToWordsOptions): st if (groupValue === 0) continue; const scale = highestScale - index; - const scaleWord = SCALE_WORDS[scale]; - - const groupGender = scale >= 2 ? undefined : gender; - - const groupText = - scale === 1 && groupValue === 1 - ? scaleWord.singular - : scale === 0 - ? groupToWords(groupValue, groupGender) - : `${groupToWords(groupValue, groupGender)} ${groupValue === 1 ? scaleWord.singular : scaleWord.plural}`; + const groupText = scaledGroupToWords(groupValue, scale, scale >= 2 ? undefined : gender); if (result === "") { result = groupText; diff --git a/src/_internals/test/runtime.ts b/src/_internals/test/runtime.ts index 85a88f94..8e371751 100644 --- a/src/_internals/test/runtime.ts +++ b/src/_internals/test/runtime.ts @@ -14,12 +14,13 @@ type RuntimeModule = { vi: { fn: (...args: any[]) => any; restoreAllMocks: () => void }; }; -const runtimeModule: RuntimeModule = - "Bun" in globalThis - ? await import("./runtime-bun") - : "Deno" in globalThis - ? await import("./runtime-deno") - : await import("./runtime-vitest"); +const loadRuntime = (): Promise => { + if ("Bun" in globalThis) return import("./runtime-bun"); + if ("Deno" in globalThis) return import("./runtime-deno"); + return import("./runtime-vitest"); +}; + +const runtimeModule = await loadRuntime(); export const { afterEach, bench, beforeEach, describe, expect, expectTypeOf, it, test, vi } = runtimeModule; diff --git a/src/convert-date-to-words/convert-date-to-words.ts b/src/convert-date-to-words/convert-date-to-words.ts index 9e66f223..d73b74df 100644 --- a/src/convert-date-to-words/convert-date-to-words.ts +++ b/src/convert-date-to-words/convert-date-to-words.ts @@ -29,6 +29,11 @@ const getWeekdayIndex = (year: number, month: number, day: number): number => { return date.getUTCDay(); }; +const dayToWords = (day: number, monthStyle: boolean): string => { + if (monthStyle) return day === 1 ? "1º" : String(day); + return day === 1 ? "primeiro" : numberToWords(day); +}; + /** * Formats a date as its Brazilian Portuguese "por extenso" textual representation, e.g. * `"01/01/2024"` becomes `"primeiro de janeiro de dois mil e vinte e quatro"`. @@ -111,9 +116,8 @@ export const convertDateToWords = ( const monthName = MONTH_NAMES[month - 1]; const isMonthStyle = options?.style === "month"; - const dateWords = isMonthStyle - ? `${day === 1 ? "1º" : day} de ${monthName} de ${year}` - : `${day === 1 ? "primeiro" : numberToWords(day)} de ${monthName} de ${numberToWords(year).replaceAll(", ", " ")}`; + const yearWords = isMonthStyle ? String(year) : numberToWords(year).replaceAll(", ", " "); + const dateWords = `${dayToWords(day, isMonthStyle)} de ${monthName} de ${yearWords}`; const result = options?.weekday === true diff --git a/src/generate-cnh/generate-cnh.test.ts b/src/generate-cnh/generate-cnh.test.ts index 2b5fc376..935a2d27 100644 --- a/src/generate-cnh/generate-cnh.test.ts +++ b/src/generate-cnh/generate-cnh.test.ts @@ -12,7 +12,11 @@ const runWithRepeatedDigitsForcingRandom = (run: () => void) => { let nextDigitIndex = 0; - Math.random = () => (forcedDigitSequence[nextDigitIndex++] + 0.5) / 10; + Math.random = () => { + const digit = forcedDigitSequence[nextDigitIndex]; + nextDigitIndex += 1; + return (digit + 0.5) / 10; + }; try { run(); diff --git a/src/generate-cnpj/generate-cnpj.test.ts b/src/generate-cnpj/generate-cnpj.test.ts index 5acd2a2e..ee35e47a 100644 --- a/src/generate-cnpj/generate-cnpj.test.ts +++ b/src/generate-cnpj/generate-cnpj.test.ts @@ -49,7 +49,11 @@ describe("generateCnpj", () => { const originalRandom = Math.random; let call = 0; - Math.random = () => (digits[call++] + 0.5) / 10; + Math.random = () => { + const digit = digits[call]; + call += 1; + return (digit + 0.5) / 10; + }; try { const cnpj = generateCnpj(1); diff --git a/src/generate-cpf/generate-cpf.test.ts b/src/generate-cpf/generate-cpf.test.ts index 83498bf3..6bb2dfdf 100644 --- a/src/generate-cpf/generate-cpf.test.ts +++ b/src/generate-cpf/generate-cpf.test.ts @@ -23,7 +23,11 @@ describe("generateCpf", () => { const originalRandom = Math.random; let call = 0; - Math.random = () => (digits[call++] + 0.5) / 10; + Math.random = () => { + const digit = digits[call]; + call += 1; + return (digit + 0.5) / 10; + }; try { const cpf = generateCpf(); diff --git a/src/generate-pis/generate-pis.test.ts b/src/generate-pis/generate-pis.test.ts index cd083015..2abab4e2 100644 --- a/src/generate-pis/generate-pis.test.ts +++ b/src/generate-pis/generate-pis.test.ts @@ -21,7 +21,11 @@ describe("generatePis", () => { const originalRandom = Math.random; let call = 0; - Math.random = () => (digits[call++] + 0.5) / 10; + Math.random = () => { + const digit = digits[call]; + call += 1; + return (digit + 0.5) / 10; + }; try { const pis = generatePis(); diff --git a/src/get-address-info-by-cep/get-address-info-by-cep.test.ts b/src/get-address-info-by-cep/get-address-info-by-cep.test.ts index 4c37a759..1bf2a67a 100644 --- a/src/get-address-info-by-cep/get-address-info-by-cep.test.ts +++ b/src/get-address-info-by-cep/get-address-info-by-cep.test.ts @@ -18,6 +18,13 @@ import { getAddressInfoByCep, } from "./get-address-info-by-cep"; +type FetchInput = string | URL | Request; + +const requestUrl = (input: FetchInput): string => { + if (typeof input === "string") return input; + return input instanceof URL ? input.href : input.url; +}; + type MockResponse = { ok: boolean; status?: number; @@ -66,8 +73,8 @@ function setupFetchMock( fetchMock: ReturnType, overrides?: Partial>, ) { - fetchMock.mockImplementation((input: string | URL | Request) => { - const url = typeof input === "string" ? input : input instanceof URL ? input.href : input.url; + fetchMock.mockImplementation((input: FetchInput) => { + const url = requestUrl(input); if (url.includes("viacep.com.br")) { if (overrides?.viacep instanceof Error) { @@ -262,8 +269,8 @@ describe("getAddressInfoByCep", () => { expectDefaultAddress(result); - const requestedUrls = fetchMock.mock.calls.map(([input]: [string | URL | Request]) => - typeof input === "string" ? input : input instanceof URL ? input.href : input.url, + const requestedUrls = fetchMock.mock.calls.map(([input]: [FetchInput]) => + requestUrl(input), ); expect(requestedUrls.some((url: string) => url.includes("widenet"))).toBe(false); }); diff --git a/src/get-cep-info-by-address/get-cep-info-by-address.test.ts b/src/get-cep-info-by-address/get-cep-info-by-address.test.ts index d0b44485..5b3b5f6e 100644 --- a/src/get-cep-info-by-address/get-cep-info-by-address.test.ts +++ b/src/get-cep-info-by-address/get-cep-info-by-address.test.ts @@ -17,6 +17,19 @@ import { } from "./get-cep-info-by-address"; describe("getCepInfoByAddress", () => { + const fetchMock = vi.fn(); + const originalFetch = globalThis.fetch; + + beforeEach(() => { + globalThis.fetch = fetchMock as typeof fetch; + fetchMock.mockClear(); + }); + + afterEach(() => { + globalThis.fetch = originalFetch; + vi.restoreAllMocks(); + }); + describe("error class names", () => { it("should set name to GetCepInfoByAddressError", () => { expect(new GetCepInfoByAddressError("message").name).toBe("GetCepInfoByAddressError"); @@ -35,19 +48,6 @@ describe("getCepInfoByAddress", () => { }); }); - const fetchMock = vi.fn(); - const originalFetch = globalThis.fetch; - - beforeEach(() => { - globalThis.fetch = fetchMock as typeof fetch; - fetchMock.mockClear(); - }); - - afterEach(() => { - globalThis.fetch = originalFetch; - vi.restoreAllMocks(); - }); - const mockSocketFailureOnce = () => fetchMock.mockRejectedValueOnce( Object.assign(new TypeError("fetch failed"), { diff --git a/src/is-valid-ie/is-valid-ie.ts b/src/is-valid-ie/is-valid-ie.ts index 507eda98..c6e8927c 100644 --- a/src/is-valid-ie/is-valid-ie.ts +++ b/src/is-valid-ie/is-valid-ie.ts @@ -134,7 +134,6 @@ const validateAP: IeValidator = (ie: string) => { if (bodyInt >= 3_000_001 && bodyInt <= 3_017_000) { p = 5; - d = 0; } else if (bodyInt >= 3_017_001 && bodyInt <= 3_019_022) { p = 9; d = 1; diff --git a/src/parse-pix-key/constants.ts b/src/parse-pix-key/constants.ts index c9d7c1a1..53291696 100644 --- a/src/parse-pix-key/constants.ts +++ b/src/parse-pix-key/constants.ts @@ -13,4 +13,4 @@ export const EVP_REGEX = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a * international prefix (`+55` or `0055`) or a DDD wrapped in parentheses. A CPF mask uses only * dots and a dash, so it never matches. */ -export const PHONE_HINT_REGEX = /^(?:\+|00)\s*55|[()]/; +export const PHONE_HINT_REGEX = /(?:^(?:\+|00)\s*55)|[()]/; diff --git a/src/parse-pix-payload/parse-pix-payload.ts b/src/parse-pix-payload/parse-pix-payload.ts index 7e53a7c5..ba0062e9 100644 --- a/src/parse-pix-payload/parse-pix-payload.ts +++ b/src/parse-pix-payload/parse-pix-payload.ts @@ -108,9 +108,9 @@ const isValidAmount = (amount: string | undefined): boolean => (AMOUNT_REGEX.test(amount) && amount.length <= PIX_TRANSACTION_AMOUNT_MAX_LENGTH); type MerchantKeyInfo = { - key?: string | undefined; - url?: string | undefined; - description?: string | undefined; + key?: string; + url?: string; + description?: string; }; const resolveMerchantKeyInfo = (fields: TlvFields): MerchantKeyInfo | null => { @@ -142,12 +142,12 @@ const resolveTxid = (fields: TlvFields): string | undefined | null => { }; type OptionalPixFields = { - key?: string | undefined; - url?: string | undefined; - description?: string | undefined; - amount?: string | undefined; - txid?: string | undefined; - pointOfInitiation?: string | undefined; + key?: string; + url?: string; + description?: string; + amount?: string; + txid?: string; + pointOfInitiation?: string; }; const buildPixPayload = ( diff --git a/vite.config.ts b/vite.config.ts index 61ca8067..7ca2b736 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -2,6 +2,7 @@ import { existsSync, readdirSync } from "node:fs"; import { resolve } from "node:path"; import { transform } from "esbuild"; +import { rules as sonarjsPluginRules } from "eslint-plugin-sonarjs"; import { defineConfig } from "vite-plus"; import { type PackUserConfig } from "vite-plus/pack"; import { webdriverio } from "vite-plus/test/browser-webdriverio"; @@ -11,6 +12,14 @@ const srcDir = resolve(rootDir, "src"); type PackPlugin = Extract, unknown[]>[number]; +/** + * Every SonarJS rule as an error, so a plugin upgrade that adds rules turns them on by default; + * the rules that are off on purpose are listed right after the spread in the lint config. + */ +const sonarjsRules = Object.fromEntries( + Object.keys(sonarjsPluginRules).map((rule) => [`sonarjs/${rule}`, "error"]), +); + /** * The root build ships a UMD file for `require()`, which tsdown never pairs with a `.d.cts` * twin (that only happens for the `cjs` format). The bundled declaration has no @@ -147,6 +156,10 @@ export default defineConfig({ typeCheck: true, }, plugins: ["oxc", "typescript", "unicorn", "import", "jsdoc", "promise", "vitest"], + // SonarJS runs as an oxlint JS plugin (bug patterns, cognitive complexity, regex checks + // and test smells that the Rust plugins do not cover). Its type-aware rules are inert + // here because oxlint does not hand ESLint plugins a type checker. + jsPlugins: ["eslint-plugin-sonarjs"], categories: { correctness: "error", suspicious: "error", @@ -396,11 +409,42 @@ export default defineConfig({ "import/no-self-import": "error", "import/no-webpack-loader-syntax": "error", "import/no-anonymous-default-export": "error", + ...sonarjsRules, + // Formatting, naming and layout are vp fmt's job or are enforced by other plugins. + "sonarjs/file-header": "off", + "sonarjs/no-tab": "off", + "sonarjs/arrow-function-convention": "off", + "sonarjs/shorthand-property-grouping": "off", + "sonarjs/concise-regex": "off", + "sonarjs/no-wildcard-import": "off", + "sonarjs/elseif-without-else": "off", + "sonarjs/no-undefined-assignment": "off", + // Already gated by eslint/complexity (20), eslint/max-lines-per-function and jscpd. + "sonarjs/cyclomatic-complexity": "off", + "sonarjs/max-lines": "off", + "sonarjs/max-lines-per-function": "off", + "sonarjs/no-duplicate-string": "off", + "sonarjs/expression-complexity": "off", + "sonarjs/too-many-break-or-continue-in-loop": "off", + "sonarjs/nested-control-flow": "off", + // TypeScript utility types (Record, Pick, ...) are reported as undeclared references. + "sonarjs/no-reference-error": "off", + // The union of the 27 state codes and the generators' Math.random are intentional. + "sonarjs/max-union-size": "off", + "sonarjs/pseudo-random": "off", + // Deprecated aliases kept for backwards compatibility are redundant by design. + "sonarjs/redundant-type-aliases": "off", + // test.todo is a runtime shim feature, not a pending task. + "sonarjs/todo-tag": "off", + "sonarjs/cognitive-complexity": ["error", 25], + "sonarjs/regex-complexity": ["error", { threshold: 25 }], }, overrides: [ { files: ["src/_internals/test/**"], rules: { + "sonarjs/no-implicit-dependencies": "off", + "sonarjs/no-nested-functions": "off", "typescript/consistent-type-definitions": "off", "vitest/no-disabled-tests": "off", "vitest/valid-title": "off", @@ -426,6 +470,7 @@ export default defineConfig({ "vitest/no-conditional-in-test": "off", "unicorn/new-for-builtins": "off", "eslint/no-new-wrappers": "off", + "sonarjs/no-primitive-wrappers": "off", "vitest/no-conditional-expect": "off", "eslint/max-lines-per-function": "off", "typescript/explicit-function-return-type": "off", @@ -441,6 +486,8 @@ export default defineConfig({ files: ["scripts/**/*.ts", "vite.config.ts"], rules: { "eslint/no-console": "off", + // The dataset scripts parse trusted official sources with backtracking regexes. + "sonarjs/super-linear-regex": "off", "import/no-default-export": "off", }, }, From a8a3987d57461c9e58fa0dc18c67db01b416416f Mon Sep 17 00:00:00 2001 From: Hyan Mandian <5044101+hyanmandian@users.noreply.github.com> Date: Sat, 12 Sep 2026 14:48:16 -0300 Subject: [PATCH 08/11] ci(security): publish an OpenSSF Scorecard for the repository --- .github/workflows/security.yml | 32 ++++++++++++++++++++++++++++++++ CONTRIBUTING.md | 6 +++++- README.md | 2 +- 3 files changed, 38 insertions(+), 2 deletions(-) diff --git a/.github/workflows/security.yml b/.github/workflows/security.yml index 5287546b..6f1cefc8 100644 --- a/.github/workflows/security.yml +++ b/.github/workflows/security.yml @@ -5,6 +5,9 @@ on: types: [opened, synchronize, reopened, ready_for_review] push: branches: [main] + schedule: + - cron: "0 6 * * 1" + workflow_dispatch: permissions: contents: read @@ -48,3 +51,32 @@ jobs: with: scan-args: | --lockfile=package-lock.json + + scorecard: + name: OpenSSF Scorecard + # Scorecard only analyses the default branch, so pull requests are skipped. + if: ${{ github.event_name != 'pull_request' }} + runs-on: ubuntu-latest + timeout-minutes: 15 + permissions: + contents: read + id-token: write + security-events: write + + steps: + - name: Checkout code + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + + - name: Run Scorecard + uses: ossf/scorecard-action@2d1146689b8cda280b9bc96326124645441f03bc # v2.4.4 + with: + results_file: results.sarif + results_format: sarif + publish_results: true + + - name: Upload the results to code scanning + uses: github/codeql-action/upload-sarif@b96794f015dfd88f77b49b1c93e0fa7110f94c63 # v4.38.0 + with: + sarif_file: results.sarif diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index adac928f..933c0106 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -224,7 +224,11 @@ signatures are pinned by the `describe(" types")` blocks in the tests, and - The `Security` workflow lints the workflows themselves with [actionlint](https://github.com/rhysd/actionlint) and [zizmor](https://github.com/zizmorcore/zizmor) and scans `package-lock.json` with [OSV-Scanner](https://google.github.io/osv-scanner/); the - `Check` workflow runs `audit-ci` and lockfile-lint on top. + `Check` workflow runs `audit-ci` and lockfile-lint on top. The same workflow runs the + [OpenSSF Scorecard](https://scorecard.dev/viewer/?uri=github.com/brazilian-utils/javascript) on + every push to `main` and weekly: it grades the repository configuration (pinned actions, token + permissions, branch protection, code review, dependency updates, SAST) rather than the code, + publishes the score and uploads the findings to the Security tab. - Commit messages are checked with commitlint on every pull request, since release-please derives the version bump and the changelog from them. - The `Links` workflow checks every URL in the Markdown files and in the `@see` tags of the source diff --git a/README.md b/README.md index 8446aae9..cc0aba36 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ [![npm version](https://img.shields.io/npm/v/@brazilian-utils/brazilian-utils.svg)](https://www.npmjs.com/package/@brazilian-utils/brazilian-utils) [![Downloads per month](https://img.shields.io/npm/dm/@brazilian-utils/brazilian-utils.svg)](https://www.npmjs.com/package/@brazilian-utils/brazilian-utils) [![License: MIT](https://img.shields.io/github/license/brazilian-utils/javascript.svg)](LICENSE) [![Zero dependencies](https://img.shields.io/badge/dependencies-0-brightgreen)](CONTRIBUTING.md#zero-runtime-dependencies) [![Bundle size](https://img.shields.io/bundlephobia/minzip/@brazilian-utils/brazilian-utils?label=isValidCpf%20import%20%3C%201%20KB&color=brightgreen)](docs/getting-started.md#bundle-size) [![Tree-shakeable](https://badgen.net/bundlephobia/tree-shaking/@brazilian-utils/brazilian-utils)](docs/getting-started.md#bundle-size) [![TypeScript](https://img.shields.io/npm/types/@brazilian-utils/brazilian-utils)](https://www.npmjs.com/package/@brazilian-utils/brazilian-utils) -[![Build Status](https://github.com/brazilian-utils/javascript/actions/workflows/build.yml/badge.svg?branch=main)](https://github.com/brazilian-utils/javascript/actions/workflows/build.yml?query=branch%3Amain) [![Tests](https://github.com/brazilian-utils/javascript/actions/workflows/tests.yml/badge.svg?branch=main)](https://github.com/brazilian-utils/javascript/actions/workflows/tests.yml?query=branch%3Amain) [![codecov](https://codecov.io/gh/brazilian-utils/javascript/branch/main/graph/badge.svg)](https://codecov.io/gh/brazilian-utils/javascript) [![Mutation tests](https://github.com/brazilian-utils/javascript/actions/workflows/mutation.yml/badge.svg?branch=main)](https://github.com/brazilian-utils/javascript/actions/workflows/mutation.yml?query=branch%3Amain) +[![Build Status](https://github.com/brazilian-utils/javascript/actions/workflows/build.yml/badge.svg?branch=main)](https://github.com/brazilian-utils/javascript/actions/workflows/build.yml?query=branch%3Amain) [![Tests](https://github.com/brazilian-utils/javascript/actions/workflows/tests.yml/badge.svg?branch=main)](https://github.com/brazilian-utils/javascript/actions/workflows/tests.yml?query=branch%3Amain) [![codecov](https://codecov.io/gh/brazilian-utils/javascript/branch/main/graph/badge.svg)](https://codecov.io/gh/brazilian-utils/javascript) [![Mutation tests](https://github.com/brazilian-utils/javascript/actions/workflows/mutation.yml/badge.svg?branch=main)](https://github.com/brazilian-utils/javascript/actions/workflows/mutation.yml?query=branch%3Amain) [![OpenSSF Scorecard](https://api.scorecard.dev/projects/github.com/brazilian-utils/javascript/badge)](https://scorecard.dev/viewer/?uri=github.com/brazilian-utils/javascript) From bcc853af32a7e3208cb2cb5b6281d2c214c5336b Mon Sep 17 00:00:00 2001 From: Hyan Mandian <5044101+hyanmandian@users.noreply.github.com> Date: Sat, 12 Sep 2026 14:48:17 -0300 Subject: [PATCH 09/11] ci(release): attach a CycloneDX SBOM to every GitHub release --- .github/workflows/release.yml | 30 ++++++++++++++++++++++++++++++ CONTRIBUTING.md | 4 ++++ 2 files changed, 34 insertions(+) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 27b33261..a816df8b 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -108,3 +108,33 @@ jobs: - name: Stage on npm run: npm stage publish --provenance --access public + + sbom: + name: Attach the SBOM to the release + needs: release-please + if: ${{ needs.release-please.outputs.release_created == 'true' }} + runs-on: ubuntu-latest + timeout-minutes: 10 + permissions: + contents: write + + steps: + - name: Checkout the release tag + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + ref: ${{ needs.release-please.outputs.tag_name }} + persist-credentials: false + + - name: Setup + uses: ./.github/actions/setup + + - name: Generate the CycloneDX SBOM of the published package + # The package has no runtime dependencies, so the SBOM describes the package itself; + # --package-lock-only reads the lockfile instead of installing anything. + run: npm sbom --sbom-format cyclonedx --omit dev --package-lock-only > brazilian-utils.cdx.json + + - name: Upload the SBOM as a release asset + env: + GH_TOKEN: ${{ github.token }} + TAG_NAME: ${{ needs.release-please.outputs.tag_name }} + run: gh release upload "$TAG_NAME" brazilian-utils.cdx.json --clobber diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 933c0106..8cedc81d 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -229,6 +229,10 @@ signatures are pinned by the `describe(" types")` blocks in the tests, and every push to `main` and weekly: it grades the repository configuration (pinned actions, token permissions, branch protection, code review, dependency updates, SAST) rather than the code, publishes the score and uploads the findings to the Security tab. +- Every GitHub release carries `brazilian-utils.cdx.json`, a CycloneDX SBOM of the published + package generated with `npm sbom` from the release tag. The package has no runtime dependencies, + so the document describes the package itself; it exists for consumers whose supply-chain policy + requires one. - Commit messages are checked with commitlint on every pull request, since release-please derives the version bump and the changelog from them. - The `Links` workflow checks every URL in the Markdown files and in the `@see` tags of the source From 5c34f470ce086514060ed9edd66ad52a5140bd4f Mon Sep 17 00:00:00 2001 From: Hyan Mandian <5044101+hyanmandian@users.noreply.github.com> Date: Sat, 12 Sep 2026 16:19:01 -0300 Subject: [PATCH 10/11] fix(phone): read a bare 55 before a service number as the country code --- .../resolve-service-phone-digits.test.ts | 28 +++++++++++++++++++ .../resolve-service-phone-digits.ts | 23 +++++++++++++++ .../strip-phone-country-code.test.ts | 2 ++ src/format-phone/format-phone.test.ts | 8 ++++++ src/format-phone/format-phone.ts | 8 ++++-- src/is-valid-phone/is-valid-phone.test.ts | 6 ++++ src/is-valid-phone/is-valid-phone.ts | 5 ++-- 7 files changed, 75 insertions(+), 5 deletions(-) create mode 100644 src/_internals/resolve-service-phone-digits/resolve-service-phone-digits.test.ts create mode 100644 src/_internals/resolve-service-phone-digits/resolve-service-phone-digits.ts diff --git a/src/_internals/resolve-service-phone-digits/resolve-service-phone-digits.test.ts b/src/_internals/resolve-service-phone-digits/resolve-service-phone-digits.test.ts new file mode 100644 index 00000000..661a983d --- /dev/null +++ b/src/_internals/resolve-service-phone-digits/resolve-service-phone-digits.test.ts @@ -0,0 +1,28 @@ +import { describe, expect, test } from "../test/runtime"; +import { resolveServicePhoneDigits } from "./resolve-service-phone-digits"; + +describe("resolveServicePhoneDigits", () => { + test("should drop a bare 55 when the national number left behind is a service number", () => { + expect(resolveServicePhoneDigits("5508001234567")).toBe("08001234567"); + expect(resolveServicePhoneDigits("55 0300 123 4567")).toBe("03001234567"); + expect(resolveServicePhoneDigits(5_508_001_234_567)).toBe("08001234567"); + }); + + test("should drop an explicit +55 or 0055 prefix", () => { + expect(resolveServicePhoneDigits("+55 0800 123 4567")).toBe("08001234567"); + expect(resolveServicePhoneDigits("0055 4004-1234")).toBe("40041234"); + expect(resolveServicePhoneDigits("+55 190")).toBe("190"); + }); + + test("should keep a bare 55 that is not followed by a service number", () => { + expect(resolveServicePhoneDigits("55190")).toBe("55190"); + expect(resolveServicePhoneDigits("5540041234")).toBe("5540041234"); + expect(resolveServicePhoneDigits("5511987654321")).toBe("5511987654321"); + }); + + test("should only sanitize a value without a country code", () => { + expect(resolveServicePhoneDigits("0800 123 4567")).toBe("08001234567"); + expect(resolveServicePhoneDigits("(11) 98765-4321")).toBe("11987654321"); + expect(resolveServicePhoneDigits("")).toBe(""); + }); +}); diff --git a/src/_internals/resolve-service-phone-digits/resolve-service-phone-digits.ts b/src/_internals/resolve-service-phone-digits/resolve-service-phone-digits.ts new file mode 100644 index 00000000..abab4ccb --- /dev/null +++ b/src/_internals/resolve-service-phone-digits/resolve-service-phone-digits.ts @@ -0,0 +1,23 @@ +import { isValidServicePhone } from "../../is-valid-service-phone/is-valid-service-phone"; +import { normalizePhone } from "../normalize-phone/normalize-phone"; +import { stripPhoneCountryCode } from "../strip-phone-country-code/strip-phone-country-code"; + +/** + * Resolves the digits a service-number check should look at, so that every phone utility reads + * the same value as a service number. + * + * When `value` carries a Brazilian country code under the `parsePhone` rule (`+55`, `0055`, or + * a bare `55` followed by 10 or 11 digits) and the national number left behind is a service + * number, that national number is returned. Otherwise the digits after an explicit `+55`/`0055` + * prefix are returned, which keeps short numbers such as `+55 190` working and leaves a bare + * `55` in place, since it is also a DDD. + * @param {string|number} value - The phone number, with or without a country code. + * @returns {string} The digits to run the service-number check on. + */ +export const resolveServicePhoneDigits = (value: string | number): string => { + const national = normalizePhone(value); + + if (isValidServicePhone(national)) return national; + + return stripPhoneCountryCode(value); +}; diff --git a/src/_internals/strip-phone-country-code/strip-phone-country-code.test.ts b/src/_internals/strip-phone-country-code/strip-phone-country-code.test.ts index 9d01fcee..11d18cfd 100644 --- a/src/_internals/strip-phone-country-code/strip-phone-country-code.test.ts +++ b/src/_internals/strip-phone-country-code/strip-phone-country-code.test.ts @@ -17,6 +17,8 @@ describe("stripPhoneCountryCode", () => { test("should only sanitize a value without a prefix", () => { expect(stripPhoneCountryCode("(11) 98765-4321")).toBe("11987654321"); + expect(stripPhoneCountryCode("11 3000 5500")).toBe("1130005500"); + expect(stripPhoneCountryCode("11 98765-4321 +55")).toBe("1198765432155"); expect(stripPhoneCountryCode(190)).toBe("190"); expect(stripPhoneCountryCode("")).toBe(""); }); diff --git a/src/format-phone/format-phone.test.ts b/src/format-phone/format-phone.test.ts index 3d4a0391..807d6cf1 100644 --- a/src/format-phone/format-phone.test.ts +++ b/src/format-phone/format-phone.test.ts @@ -13,6 +13,14 @@ describe("formatPhone", () => { expect(formatPhone("55 4004-1234", { mask: "e164" })).toBe("+555540041234"); }); + it("should format a service number written with a bare country code", () => { + expect(formatPhone("5508001234567", { mask: "auto" })).toBe("0800 123 4567"); + expect(formatPhone("5508001234567", { mask: "e164" })).toBe("0800 123 4567"); + expect(formatPhone("5508001234567", { mask: "international" })).toBe("0800 123 4567"); + expect(formatPhone("5508001234567", { mask: "service" })).toBe("0800 123 4567"); + expect(formatPhone("55 0300 123 4567", { mask: "auto" })).toBe("0300 123 4567"); + }); + it("should sn format phone", () => { expect(formatPhone("")).toBe(""); expect(formatPhone("9")).toBe("9"); diff --git a/src/format-phone/format-phone.ts b/src/format-phone/format-phone.ts index 185504fc..1490b7fb 100644 --- a/src/format-phone/format-phone.ts +++ b/src/format-phone/format-phone.ts @@ -7,8 +7,8 @@ import { import { format } from "../_internals/format/format"; import { isNullish } from "../_internals/is-nullish/is-nullish"; import { normalizePhone } from "../_internals/normalize-phone/normalize-phone"; +import { resolveServicePhoneDigits } from "../_internals/resolve-service-phone-digits/resolve-service-phone-digits"; import { sanitizeToDigits } from "../_internals/sanitize-to-digits/sanitize-to-digits"; -import { stripPhoneCountryCode } from "../_internals/strip-phone-country-code/strip-phone-country-code"; import { isValidServicePhone } from "../is-valid-service-phone/is-valid-service-phone"; import { INTERNATIONAL_MASK, INTERNATIONAL_PREFIX, LENGTH, MASK, SERVICE_MASK } from "./constants"; @@ -91,7 +91,8 @@ const resolveAutoMask = (digits: string, serviceDigits: string): Exclude { test("should accept an explicit country code before a service number", () => { expect(isValidPhone("+55 0800 123 4567", { accept: ["service"] })).toBe(true); expect(isValidPhone("0055 4004-1234", { accept: ["service"] })).toBe(true); + }); + + test("should accept a bare 55 before a service number, as parsePhone reads it", () => { + expect(isValidPhone("5508001234567", { accept: ["service"] })).toBe(true); + expect(isValidPhone("55 0300 123 4567", { accept: ["service"] })).toBe(true); + expect(isValidPhone("5508001234567")).toBe(false); expect(isValidPhone("+55 0800 123 4567")).toBe(false); }); }); diff --git a/src/is-valid-phone/is-valid-phone.ts b/src/is-valid-phone/is-valid-phone.ts index 6f62e1c6..a0ae3ea3 100644 --- a/src/is-valid-phone/is-valid-phone.ts +++ b/src/is-valid-phone/is-valid-phone.ts @@ -3,7 +3,7 @@ import { PHONE_NATIONAL_MIN_LENGTH, } from "../_internals/constants/phone"; import { normalizePhone } from "../_internals/normalize-phone/normalize-phone"; -import { stripPhoneCountryCode } from "../_internals/strip-phone-country-code/strip-phone-country-code"; +import { resolveServicePhoneDigits } from "../_internals/resolve-service-phone-digits/resolve-service-phone-digits"; import { isValidLandlinePhone } from "../is-valid-landline-phone/is-valid-landline-phone"; import { isValidMobilePhone } from "../is-valid-mobile-phone/is-valid-mobile-phone"; import { isValidServicePhone } from "../is-valid-service-phone/is-valid-service-phone"; @@ -58,7 +58,8 @@ export const isValidPhone = (value: string, options?: IsValidPhoneOptions): bool const requested = options?.accept; const accept: PhoneType[] = Array.isArray(requested) ? requested : DEFAULT_ACCEPT; - if (accept.includes("service") && isValidServicePhone(stripPhoneCountryCode(value))) return true; + if (accept.includes("service") && isValidServicePhone(resolveServicePhoneDigits(value))) + return true; const digits = normalizePhone(value); From 3ab1b44e1b260035c9b828caa692618a4b92598f Mon Sep 17 00:00:00 2001 From: Hyan Mandian <5044101+hyanmandian@users.noreply.github.com> Date: Sat, 12 Sep 2026 16:46:59 -0300 Subject: [PATCH 11/11] docs: state the supported Node.js range instead of 20+ in the feature list --- README.md | 2 +- docs/getting-started.md | 2 +- docs/llms-full.txt | 2 +- docs/pt-br/getting-started.md | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index cc0aba36..2423b6d2 100644 --- a/README.md +++ b/README.md @@ -33,7 +33,7 @@ Brazilian Utils is a library focused on solving problems that we face daily in t - **Zero runtime dependencies.** Nothing else lands in your `node_modules` or in your bundle. - **Tree-shakeable, down to the function.** `import { isValidCpf }` costs under 1 KB; every util is also its own subpath entry (`@brazilian-utils/brazilian-utils/get-cities`) for the heavy ones. -- **Runs everywhere.** Node.js 20+, Bun, Deno and evergreen browsers, tested in CI on every one of them. +- **Runs everywhere.** Node.js `^20.19.0 || >=22.12.0`, Bun, Deno and evergreen browsers, tested in CI on every one of them. - **Written in TypeScript.** Types ship with the package; the public API is tracked by an API report so nothing changes silently. - **Validated against the official rules.** Every validator cites the specification, law or dataset it implements (`@see` in the docs), and the test suite is mutation-tested, not just covered. - **Documented in English and Portuguese**, with an `llms.txt` for AI assistants. diff --git a/docs/getting-started.md b/docs/getting-started.md index 1505c823..8027214e 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -6,7 +6,7 @@ Brazilian Utils is a library focused on solving problems that we face daily in t - **Zero runtime dependencies.** Nothing else lands in your `node_modules` or in your bundle. - **Tree-shakeable, down to the function.** `import { isValidCpf }` costs under 1 KB; every util is also its own subpath entry (`@brazilian-utils/brazilian-utils/get-cities`) for the heavy ones. -- **Runs everywhere.** Node.js 20+, Bun, Deno and evergreen browsers, tested in CI on every one of them. +- **Runs everywhere.** Node.js `^20.19.0 || >=22.12.0`, Bun, Deno and evergreen browsers, tested in CI on every one of them. - **Written in TypeScript.** Types ship with the package; the public API is tracked by an API report so nothing changes silently. - **Validated against the official rules.** Every validator cites the specification, law or dataset it implements (`@see` in the docs), and the test suite is mutation-tested, not just covered. - **Documented in English and Portuguese**, with an `llms.txt` for AI assistants. diff --git a/docs/llms-full.txt b/docs/llms-full.txt index eddb6bd5..2cf83ca3 100644 --- a/docs/llms-full.txt +++ b/docs/llms-full.txt @@ -143,7 +143,7 @@ Brazilian Utils is a library focused on solving problems that we face daily in t - **Zero runtime dependencies.** Nothing else lands in your `node_modules` or in your bundle. - **Tree-shakeable, down to the function.** `import { isValidCpf }` costs under 1 KB; every util is also its own subpath entry (`@brazilian-utils/brazilian-utils/get-cities`) for the heavy ones. -- **Runs everywhere.** Node.js 20+, Bun, Deno and evergreen browsers, tested in CI on every one of them. +- **Runs everywhere.** Node.js `^20.19.0 || >=22.12.0`, Bun, Deno and evergreen browsers, tested in CI on every one of them. - **Written in TypeScript.** Types ship with the package; the public API is tracked by an API report so nothing changes silently. - **Validated against the official rules.** Every validator cites the specification, law or dataset it implements (`@see` in the docs), and the test suite is mutation-tested, not just covered. - **Documented in English and Portuguese**, with an `llms.txt` for AI assistants. diff --git a/docs/pt-br/getting-started.md b/docs/pt-br/getting-started.md index 5e647c79..8f28bc9a 100644 --- a/docs/pt-br/getting-started.md +++ b/docs/pt-br/getting-started.md @@ -6,7 +6,7 @@ Brazilian Utils é uma biblioteca com foco na resolução de problemas que enfre - **Zero dependências de runtime.** Nada além da lib entra no seu `node_modules` ou no seu bundle. - **Tree-shakeable até a função.** `import { isValidCpf }` custa menos de 1 KB; cada utilitário também é um subpath próprio (`@brazilian-utils/brazilian-utils/get-cities`) para os mais pesados. -- **Roda em qualquer lugar.** Node.js 20+, Bun, Deno e navegadores modernos, testados no CI em todos eles. +- **Roda em qualquer lugar.** Node.js `^20.19.0 || >=22.12.0`, Bun, Deno e navegadores modernos, testados no CI em todos eles. - **Escrita em TypeScript.** Os tipos vêm no pacote; a API pública é acompanhada por um relatório de API, então nada muda em silêncio. - **Validada contra as regras oficiais.** Cada validador cita a especificação, lei ou base de dados que implementa (`@see` na documentação), e a suíte de testes passa por mutation testing, não só por cobertura. - **Documentada em inglês e português**, com um `llms.txt` para assistentes de IA.