From c20c17459e84a8122a941197519e8a14658cf6ce Mon Sep 17 00:00:00 2001
From: Erik Golinelli <erik@codekraft.it>
Date: Thu, 16 May 2024 11:05:06 +0200
Subject: [PATCH 1/3] adds strict type definitions to sprintf

---
 packages/sprintf/.gitignore       |  1 +
 packages/sprintf/index.js         | 25 +++++++++----------------
 packages/sprintf/types/index.d.ts | 26 ++++++++++++++++++++++++++
 3 files changed, 36 insertions(+), 16 deletions(-)
 create mode 100644 packages/sprintf/.gitignore
 create mode 100644 packages/sprintf/types/index.d.ts

diff --git a/packages/sprintf/.gitignore b/packages/sprintf/.gitignore
new file mode 100644
index 0000000..6377d49
--- /dev/null
+++ b/packages/sprintf/.gitignore
@@ -0,0 +1 @@
+!types/*
diff --git a/packages/sprintf/index.js b/packages/sprintf/index.js
index 31ca82f..e0f0836 100644
--- a/packages/sprintf/index.js
+++ b/packages/sprintf/index.js
@@ -53,26 +53,19 @@ var PATTERN =
  * sprintf( 'Hello %s!', 'world' );
  * // ⇒ 'Hello world!'
  * ```
- *
- * @param {string}                                   string printf format string
- * @param {...string|string[]|Object<string,string>} [args] String arguments.
+ * @template {string} T
+ * @param {T} string - string printf format string
+ * @param {import('./types/index.d').SprintfArgs<T>|import('./types/index.d').SprintfArgs<T>[]|undefined} args String arguments.
  *
  * @return {string} Formatted string.
  */
-export default function sprintf(string, args) {
-	var i;
+export default function sprintf(string, ...args) {
+	var i = 0;
 
-	if (!Array.isArray(args)) {
-		// Construct a copy of arguments from index one, used for replace
-		// function placeholder substitution.
-		args = new Array(arguments.length - 1);
-		for (i = 1; i < arguments.length; i++) {
-			args[i - 1] = arguments[i];
-		}
+	if (Array.isArray(args[0])) {
+		args = /** @type {import('./types/index.d').SprintfArgs<T>[]} */ args[0];
 	}
 
-	i = 1;
-
 	return string.replace(PATTERN, function () {
 		var index, name, precision, type, value;
 
@@ -89,14 +82,14 @@ export default function sprintf(string, args) {
 
 		// Asterisk precision determined by peeking / shifting next argument.
 		if (precision === '*') {
-			precision = args[i - 1];
+			precision = args[i];
 			i++;
 		}
 
 		if (name === undefined) {
 			// If not a positional argument, use counter value.
 			if (index === undefined) {
-				index = i;
+				index = i + 1;
 			}
 
 			i++;
diff --git a/packages/sprintf/types/index.d.ts b/packages/sprintf/types/index.d.ts
new file mode 100644
index 0000000..8e0c2d0
--- /dev/null
+++ b/packages/sprintf/types/index.d.ts
@@ -0,0 +1,26 @@
+type Specifiers = {
+	's': string,
+	'd': number,
+	'b': boolean,
+	'D': Date
+};
+type S = keyof Specifiers;
+
+type ExtractNamedPlaceholders<T extends string> =
+	T extends `${any}%(${infer Key})${infer Spec}${infer Rest}`
+		? Spec extends S
+			? { [K in Key]: Specifiers[Spec]} & ExtractNamedPlaceholders<Rest>
+			: never
+		: {};
+
+type ExtractUnnamedPlaceholders<T extends string> =
+	T extends `${any}%${infer Spec}${infer Rest}`
+		? Spec extends S
+			? [Specifiers[Spec], ...ExtractUnnamedPlaceholders<Rest>]
+			: never
+		: [];
+
+export type SprintfArgs<T extends string> =
+	ExtractUnnamedPlaceholders<T> extends never
+		? [values: ExtractNamedPlaceholders<T>]
+		: ExtractUnnamedPlaceholders<T>;

From 91c83e25c73d83b0537e7d39837ecd50835579f0 Mon Sep 17 00:00:00 2001
From: Erik Golinelli <erik@codekraft.it>
Date: Thu, 16 May 2024 15:10:02 +0200
Subject: [PATCH 2/3] types updated to match the supported specifiers (%d %f
 and %s)

---
 packages/sprintf/index.js         | 2 +-
 packages/sprintf/types/index.d.ts | 3 +--
 2 files changed, 2 insertions(+), 3 deletions(-)

diff --git a/packages/sprintf/index.js b/packages/sprintf/index.js
index e0f0836..7d9799e 100644
--- a/packages/sprintf/index.js
+++ b/packages/sprintf/index.js
@@ -55,7 +55,7 @@ var PATTERN =
  * ```
  * @template {string} T
  * @param {T} string - string printf format string
- * @param {import('./types/index.d').SprintfArgs<T>|import('./types/index.d').SprintfArgs<T>[]|undefined} args String arguments.
+ * @param {import('./types/index.d').SprintfArgs<T>|import('./types/index.d').SprintfArgs<T>[]} args String arguments.
  *
  * @return {string} Formatted string.
  */
diff --git a/packages/sprintf/types/index.d.ts b/packages/sprintf/types/index.d.ts
index 8e0c2d0..ed926a6 100644
--- a/packages/sprintf/types/index.d.ts
+++ b/packages/sprintf/types/index.d.ts
@@ -1,8 +1,7 @@
 type Specifiers = {
 	's': string,
 	'd': number,
-	'b': boolean,
-	'D': Date
+	'f': number
 };
 type S = keyof Specifiers;
 

From f3923f985606bc0767f8b180f51495b91ce753b0 Mon Sep 17 00:00:00 2001
From: Erik Golinelli <erik@codekraft.it>
Date: Fri, 17 May 2024 12:32:52 +0200
Subject: [PATCH 3/3] includes types/index.d.ts to the published files

---
 packages/sprintf/package.json | 1 +
 1 file changed, 1 insertion(+)

diff --git a/packages/sprintf/package.json b/packages/sprintf/package.json
index 04d3653..85bb1f5 100644
--- a/packages/sprintf/package.json
+++ b/packages/sprintf/package.json
@@ -31,6 +31,7 @@
 	"files": [
 		"index.js",
 		"index.d.ts",
+		"types/index.d.ts",
 		"build",
 		"dist"
 	],