Skip to content

Commit 8a8fee0

Browse files
feature/add-parameterized-utility-function (#80)
1 parent 5c847d9 commit 8a8fee0

File tree

3 files changed

+261
-1
lines changed

3 files changed

+261
-1
lines changed

README.md

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1516,6 +1516,46 @@ console.log(parseValue("String value")); // Outputs: 'String value'
15161516

15171517
---
15181518

1519+
##### `parameterized`
1520+
1521+
Creates a parameterized string with placeholder values.
1522+
1523+
###### Types
1524+
1525+
###### `ParameterizedValue`
1526+
1527+
Represents a value that can be used as a parameter in string operations.
1528+
1529+
```ts
1530+
type ParameterizedValue = string | boolean | number | bigint;
1531+
```
1532+
1533+
###### `Parameterized`
1534+
1535+
Represents a parameterized string with its corresponding values.
1536+
1537+
```ts
1538+
type Parameterized = [string, ParameterizedValue[]];
1539+
```
1540+
1541+
<details>
1542+
1543+
<summary style="cursor:pointer">Examples</summary>
1544+
1545+
```ts
1546+
import { parameterized } from "@alessiofrittoli/web-utils";
1547+
1548+
const data = {
1549+
value: "parameterized",
1550+
};
1551+
1552+
console.log(parameterized`My string with ${data.value} values.`); // [ 'My string with ? values.', [ 'parameterized' ] ]
1553+
```
1554+
1555+
</details>
1556+
1557+
---
1558+
15191559
#### Types utilities
15201560

15211561
⚠️ Docs coming soon

__tests__/strings.test.ts

Lines changed: 151 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { lcFirst, parseValue, stringifyValue, toCamelCase, toKebabCase, ucFirst } from '@/strings'
1+
import { lcFirst, parameterized, parseValue, stringifyValue, toCamelCase, toKebabCase, ucFirst } from '@/strings'
22
import { addLeadingCharacter, addTrailingCharacter, removeLeadingCharacter, removeTrailingCharacter } from '@/strings'
33
import { recipientsToString, emailDataToString } from '@/strings'
44

@@ -607,4 +607,154 @@ describe( 'emailDataToString', () => {
607607
)
608608
} )
609609

610+
} )
611+
612+
613+
describe( 'parameterized', () => {
614+
615+
it( 'creates a parameterized string with a single value', () => {
616+
617+
const [ string, params ] = parameterized`SELECT * FROM users WHERE id = ${ 1 }`
618+
619+
expect( string ).toBe( 'SELECT * FROM users WHERE id = ?' )
620+
expect( params ).toEqual( [ 1 ] )
621+
622+
} )
623+
624+
625+
it( 'creates a parameterized string with multiple single values', () => {
626+
627+
const [ string, params ] = parameterized`SELECT * FROM users WHERE id = ${ 1 } AND name = ${ 'John' }`
628+
629+
expect( string ).toBe( 'SELECT * FROM users WHERE id = ? AND name = ?' )
630+
expect( params ).toEqual( [ 1, 'John' ] )
631+
632+
} )
633+
634+
635+
it( 'expands array values into multiple placeholders', () => {
636+
637+
const [ string, params ] = parameterized`SELECT * FROM users WHERE status IN ( ${ [ 'active', 'pending' ] } )`
638+
639+
expect( string ).toBe( 'SELECT * FROM users WHERE status IN ( ?, ? )' )
640+
expect( params ).toEqual( [ 'active', 'pending' ] )
641+
642+
} )
643+
644+
645+
it( 'handles mixed single and array values', () => {
646+
647+
const [ string, params ] = parameterized`SELECT * FROM users WHERE id = ${ 1 } AND status IN ( ${ [ 'active', 'pending' ] } )`
648+
649+
expect( string ).toBe( 'SELECT * FROM users WHERE id = ? AND status IN ( ?, ? )' )
650+
expect( params ).toEqual( [ 1, 'active', 'pending' ] )
651+
652+
} )
653+
654+
655+
it( 'normalizes whitespace in the string string', () => {
656+
657+
const [ string, params ] = parameterized`SELECT * FROM users WHERE id = ${ 1 }`
658+
659+
expect( string ).toBe( 'SELECT * FROM users WHERE id = ?' )
660+
expect( params ).toEqual( [ 1 ] )
661+
662+
} )
663+
664+
665+
it( 'trims leading and trailing whitespace', () => {
666+
667+
const [ string, params ] = parameterized` SELECT * FROM users WHERE id = ${ 1 } `
668+
669+
expect( string ).toBe( 'SELECT * FROM users WHERE id = ?' )
670+
expect( params ).toEqual( [ 1 ] )
671+
672+
} )
673+
674+
675+
it( 'skips undefined values', () => {
676+
677+
// @ts-expect-error negative testing
678+
const [ string, params ] = parameterized`SELECT * FROM users WHERE id = ${ undefined } AND name = ${ 'John' }`
679+
680+
expect( string ).toBe( 'SELECT * FROM users WHERE id = AND name = ?' )
681+
expect( params ).toEqual( [ 'John' ] )
682+
683+
} )
684+
685+
686+
it( 'handles boolean values', () => {
687+
688+
const [ string, params ] = parameterized`SELECT * FROM users WHERE active = ${ true }`
689+
690+
expect( string ).toBe( 'SELECT * FROM users WHERE active = ?' )
691+
expect( params ).toEqual( [ true ] )
692+
693+
} )
694+
695+
696+
it( 'handles numeric values including bigint', () => {
697+
698+
const [ string, params ] = parameterized`SELECT * FROM users WHERE id = ${ 42 } OR count = ${ BigInt( 999999999999 ) }`
699+
700+
expect( string ).toBe( 'SELECT * FROM users WHERE id = ? OR count = ?' )
701+
expect( params ).toEqual( [ 42, BigInt( 999999999999 ) ] )
702+
703+
} )
704+
705+
706+
it( 'handles empty array values', () => {
707+
708+
const [ string, params ] = parameterized`SELECT * FROM users WHERE id IN (${ [] })`
709+
710+
expect( string ).toBe( 'SELECT * FROM users WHERE id IN ()' )
711+
expect( params ).toEqual( [] )
712+
713+
} )
714+
715+
716+
it( 'handles array with single value', () => {
717+
718+
const [ string, params ] = parameterized`SELECT * FROM users WHERE status IN (${ [ 'active' ] })`
719+
720+
expect( string ).toBe( 'SELECT * FROM users WHERE status IN (?)' )
721+
expect( params ).toEqual( [ 'active' ] )
722+
723+
} )
724+
725+
726+
it( 'handles multiple arrays in a string', () => {
727+
728+
const [ string, params ] = parameterized`SELECT * FROM users WHERE status IN (${ [ 'active', 'pending' ] }) AND id IN (${ [ 1, 2, 3 ] })`
729+
730+
expect( string ).toBe( 'SELECT * FROM users WHERE status IN (?, ?) AND id IN (?, ?, ?)' )
731+
expect( params ).toEqual( [ 'active', 'pending', 1, 2, 3 ] )
732+
733+
} )
734+
735+
736+
it( 'returns correct tuple structure', () => {
737+
738+
const result = parameterized`SELECT * FROM users WHERE id = ${ 1 }`
739+
740+
expect( Array.isArray( result ) ).toBe( true )
741+
expect( result.length ).toBe( 2 )
742+
expect( typeof result[ 0 ] ).toBe( 'string' )
743+
expect( Array.isArray( result[ 1 ] ) ).toBe( true )
744+
745+
} )
746+
747+
748+
it( 'handles newlines and tabs in template', () => {
749+
750+
const [ string, params ] = parameterized`
751+
SELECT * FROM users
752+
WHERE id = ${ 1 }
753+
`
754+
755+
expect( string ).toBe( 'SELECT * FROM users WHERE id = ?' )
756+
expect( params ).toEqual( [ 1 ] )
757+
758+
} )
759+
610760
} )

src/strings.ts

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -341,4 +341,74 @@ export const emailDataToString = (
341341
searchParams.size > 0 && `?${ searchParams.toString().replace( /\+/g, ' ' ) }`,
342342
].filter( Boolean ).join( '' )
343343

344+
}
345+
346+
347+
/**
348+
* Represents a value that can be used as a parameter in string operations.
349+
*
350+
*/
351+
export type ParameterizedValue = string | boolean | number | bigint
352+
353+
354+
/**
355+
* Represents a parameterized string with its corresponding values.
356+
*
357+
* @property {string} 0 - The template string
358+
* @property {ParameterizedValue[]} 1 - Array of values to be substituted into the template string
359+
*/
360+
export type Parameterized = [ string, ParameterizedValue[] ]
361+
362+
363+
/**
364+
* Creates a parameterized string with placeholder values.
365+
*
366+
* @param strings - The template string parts from a template literal.
367+
* @param values - One or more parameter values or arrays of values to be substituted into the template.
368+
* @returns A tuple containing the normalized string with `?` placeholders and an array of parameter values.
369+
*
370+
* @example
371+
* ```ts
372+
* const [ string, params ] = parameterized`
373+
* SELECT * FROM users WHERE id = ${ 1 } AND status IN ( ${ [ 'active', 'pending' ] } )
374+
* ` )
375+
*
376+
* // string: "SELECT * FROM users WHERE id = ? AND status IN ( ?, ? )"
377+
* // params: [ 1, 'active', 'pending' ]
378+
* ```
379+
*
380+
* @remarks
381+
* - Whitespace is normalized (multiple spaces reduced to single space, trimmed).
382+
* - Undefined values are skipped.
383+
* - Array values are expanded into comma-separated placeholders.
384+
* - Single values are replaced with a single `?` placeholder.
385+
*/
386+
export const parameterized = ( strings: TemplateStringsArray, ...values: ( ParameterizedValue | ParameterizedValue[] )[] ): Parameterized => {
387+
388+
const params: ParameterizedValue[] = []
389+
let text = ''
390+
391+
strings.forEach( ( string, index ) => {
392+
text += string
393+
394+
if ( index >= values.length ) return
395+
396+
const value = values[ index ]
397+
398+
if ( typeof value === 'undefined' ) return
399+
400+
if ( Array.isArray( value ) ) {
401+
402+
const placeholders = value.map( () => '?' ).join( ', ' )
403+
text += placeholders
404+
params.push( ...value )
405+
return
406+
}
407+
408+
text += '?'
409+
params.push( value )
410+
411+
} )
412+
413+
return [ text.replace( /\s+/g, ' ' ).trim(), params ]
344414
}

0 commit comments

Comments
 (0)