From 44b34b61a3084333e70aa37c64e3b5b76885938b Mon Sep 17 00:00:00 2001 From: Erik Golinelli Date: Thu, 12 Feb 2026 18:29:45 +0100 Subject: [PATCH 01/12] Refactor `getAuthorFromPackage` logic for improved type safety and readability --- src/extractors/headers.ts | 49 +++++++++++++++++++++------------------ 1 file changed, 27 insertions(+), 22 deletions(-) diff --git a/src/extractors/headers.ts b/src/extractors/headers.ts index a5ddd77..0bbf870 100644 --- a/src/extractors/headers.ts +++ b/src/extractors/headers.ts @@ -129,41 +129,46 @@ function extractAuthorData(authorData: string | object): { * @param pkgJsonData The package.json data object * @returns Author data with name, email and website */ -export function getAuthorFromPackage(pkgJsonData: PackageI18n): { - name: string; - email?: string; - website?: string; -} { +export function getAuthorFromPackage( + pkgJsonData: Record, +): AuthorData { // Check multiple possible locations for author information - const locations = [ + const fields = [ "author", // Standard author field "authors", // Some packages use authors (plural) "contributors", // Try contributors if no author - "maintainers", // Try maintainers as last resort - ]; + "maintainers", // Try maintainers as a last resort + ] as string[]; // Try each location in order - for (const location of locations) { - if (pkgJsonData[location]) { - let authorData: { - name: string; - email?: string; - website?: string; - }; - if (typeof pkgJsonData[location] === "string") { - authorData = extractAuthorData(pkgJsonData[location]); - } else if (typeof pkgJsonData[location] === "object") { - for (const author of pkgJsonData[location]) { + for (const field of fields) { + const value = pkgJsonData[field]; + if (value) { + let authorData: AuthorData | undefined; + + if (typeof value === "string") { + authorData = extractAuthorData(value); + } else if (Array.isArray(value)) { + for (const author of value) { if (!author) continue; - authorData = extractAuthorData(author); - if (authorData) break; + if ( + typeof author === "string" || + (typeof author === "object") + ) { + authorData = extractAuthorData(author as string | AuthorData); + if (authorData) break; + } } + } else if (typeof value === "object") { + // Handle single object author field + authorData = extractAuthorData(value as AuthorData); } + if ( authorData?.name !== "AUTHOR" || authorData?.email !== "AUTHOR EMAIL" ) { - return authorData; + return authorData as AuthorData; // Returns the valid author data found } } } From de3c87aba66d7b756c5deace9f656b82c4ac7c94 Mon Sep 17 00:00:00 2001 From: Erik Golinelli Date: Thu, 12 Feb 2026 18:30:56 +0100 Subject: [PATCH 02/12] Refactor `validateRequiredFields` and `extractAuthorData` for stricter typing and improved readability --- src/extractors/headers.ts | 63 ++++++++++++++++++++------------------- 1 file changed, 32 insertions(+), 31 deletions(-) diff --git a/src/extractors/headers.ts b/src/extractors/headers.ts index 0bbf870..292be8a 100644 --- a/src/extractors/headers.ts +++ b/src/extractors/headers.ts @@ -1,14 +1,12 @@ -import path from "node:path"; -import { SetOfBlocks } from "gettext-merger"; -import { boolean } from "yargs"; -import type PackageI18n from "../assets/package-i18n.js"; -import { modulePath } from "../const.js"; -import { getEncodingCharset } from "../fs/fs.js"; -import type { Args, I18nHeaders, PotHeaders } from "../types.js"; -import { getPkgJsonData } from "../utils/common.js"; -import { buildBlock } from "../utils/extractors.js"; -import { extractCssThemeData } from "./css.js"; -import { extractPhpPluginData } from "./php.js"; +import path from 'node:path' +import { SetOfBlocks } from 'gettext-merger' +import { modulePath } from '../const.js' +import { getEncodingCharset } from '../fs/fs.js' +import type { Args, AuthorData, I18nHeaders, PotHeaders } from '../types.js' +import { getPkgJsonData } from '../utils/common.js' +import { buildBlock } from '../utils/extractors.js' +import { extractCssThemeData } from './css.js' +import { extractPhpPluginData } from './php.js' /** * Checks if required fields are missing and logs a clear error message @@ -20,14 +18,20 @@ function validateRequiredFields( headerData: I18nHeaders, debug: boolean, ): boolean { - const requiredFields = [ - { key: "slug", name: "Plugin/Theme slug", placeholder: "PLUGIN NAME" }, - { key: "author", name: "Author name", placeholder: "AUTHOR" }, - { key: "version", name: "Version", placeholder: "" }, - { key: "email", name: "Author email", placeholder: "AUTHOR EMAIL" }, - { key: "xDomain", name: "Text domain", placeholder: "PLUGIN TEXTDOMAIN" }, - ]; - + // Define the required fields with strict key types + const requiredFields: { + key: keyof I18nHeaders; + name: string; + placeholder: string; + }[] = [ + { key: "slug", name: "Plugin/Theme slug", placeholder: "PLUGIN NAME" }, + { key: "author", name: "Author name", placeholder: "AUTHOR" }, + { key: "version", name: "Version", placeholder: "" }, + { key: "email", name: "Author email", placeholder: "AUTHOR EMAIL" }, + { key: "xDomain", name: "Text domain", placeholder: "PLUGIN TEXTDOMAIN" }, + ]; + + // Filter out the missing or default fields const missingFields = requiredFields.filter( (field) => !headerData[field.key] || @@ -83,11 +87,9 @@ function validateRequiredFields( * @returns an object with name, email, and website * @param authorData */ -function extractAuthorData(authorData: string | object): { - name: string; - email?: string; - website?: string; -} { +function extractAuthorData( + authorData: string | AuthorData, +): AuthorData | undefined { // Default result with placeholder values const defaultResult = { name: "AUTHOR", email: "AUTHOR EMAIL" }; @@ -192,9 +194,9 @@ function consolidateUserHeaderData(args: Args): I18nHeaders { "authors", "contributors", "maintainers", - ) as Record<[keyof PackageI18n], string>; + ); // get author data from package.json - const pkgAuthor = getAuthorFromPackage(pkgJsonData); + const pkgAuthor = getAuthorFromPackage(pkgJsonData as unknown as Record); // get the current directory name as slug const currentDir = path @@ -217,16 +219,16 @@ function consolidateUserHeaderData(args: Args): I18nHeaders { return { ...args.headers, - name: args.headers?.name || slug, + name: args.headers?.name?.toString() || slug, author: authorName, - authorString: authorString, // this is the author with email address in this format: author + authorString: authorString, // this is the author + email address in this format: author slug, email, bugs, license: args.headers?.license || "gpl-2.0 or later", - version: args.headers?.version || pkgJsonData.version || "0.0.1", + version: args.headers?.version || (pkgJsonData.version as string) || "0.0.1", language: "en", - xDomain: args.headers?.textDomain || slug, + xDomain: args.headers?.textDomain?.toString() || slug, }; } @@ -257,7 +259,6 @@ export async function generateHeader( // Validate required fields - exit early if validation fails if (!validateRequiredFields(headerData, args.debug)) { process.exit(1); // Exit with error code - return null; // This is never reached but helps with TypeScript } return { From b9ad66ac39969fb8012ae041613dccf60b047695 Mon Sep 17 00:00:00 2001 From: Erik Golinelli Date: Thu, 12 Feb 2026 18:34:05 +0100 Subject: [PATCH 03/12] Refactor multiple modules for stricter typing, improved readability, and enhanced error handling across extractors and parsers --- src/cli/parseCli.ts | 2 +- src/extractors/auditStrings.ts | 2 +- src/extractors/json.ts | 2 +- src/extractors/schema.ts | 32 +++++++++++++++----------------- src/fs/glob.ts | 17 ++++++++--------- src/parser/exec.ts | 4 ++-- src/parser/makePot.ts | 2 +- src/parser/progress.ts | 6 +++--- src/utils/common.ts | 3 +-- src/utils/extractors.ts | 2 +- 10 files changed, 34 insertions(+), 38 deletions(-) diff --git a/src/cli/parseCli.ts b/src/cli/parseCli.ts index e932667..98c9625 100644 --- a/src/cli/parseCli.ts +++ b/src/cli/parseCli.ts @@ -169,7 +169,7 @@ export function parseJsonArgs( const currentWorkingDirectory = process.cwd(); const slug = path.basename(path.resolve(currentWorkingDirectory)); - let scriptName: string; + let scriptName: string | undefined; if (args.scriptName) { scriptName = args.scriptName.split(",").map((s) => s.trim()); if (scriptName.length === 1) { diff --git a/src/extractors/auditStrings.ts b/src/extractors/auditStrings.ts index a1badb9..77d119d 100644 --- a/src/extractors/auditStrings.ts +++ b/src/extractors/auditStrings.ts @@ -15,7 +15,7 @@ export function audit(args: Args, translationsUnion: SetOfBlocks) { //if there are no errors, we can remove the audit.log file try { unlinkSync(path.join(args.paths.cwd, "audit.log")); - } catch (error) { + } catch (_error) { //ignore } } else { diff --git a/src/extractors/json.ts b/src/extractors/json.ts index fcec59a..a7879b9 100644 --- a/src/extractors/json.ts +++ b/src/extractors/json.ts @@ -16,7 +16,7 @@ import { JsonSchemaExtractor } from "./schema.js"; export async function parseJsonFile(opts: { fileContent: string; filename: "block.json" | "theme.json"; -}): Promise { +}): Promise { const isTheme = opts.filename === "theme.json"; const schema: { url: string; fallback: I18nSchema } = { url: isTheme diff --git a/src/extractors/schema.ts b/src/extractors/schema.ts index c94de57..2510d7c 100644 --- a/src/extractors/schema.ts +++ b/src/extractors/schema.ts @@ -1,7 +1,5 @@ import type { Block } from "gettext-merger"; -import type BlockI18n from "../assets/block-i18n.js"; import * as blocki18n from "../assets/block-i18n.js"; -import type ThemeI18n from "../assets/theme-i18n.js"; import * as themei18n from "../assets/theme-i18n.js"; import type { I18nSchema } from "../types.js"; @@ -14,11 +12,11 @@ export class JsonSchemaExtractor { /** Theme */ static themeJsonSource = "http://develop.svn.wordpress.org/trunk/src/wp-includes/theme-i18n.json"; - static themeJsonFallback = themei18n as ThemeI18n; + static themeJsonFallback = themei18n; /** Block */ static blockJsonSource = "http://develop.svn.wordpress.org/trunk/src/wp-includes/block-i18n.json"; - static blockJsonFallback = blocki18n as BlockI18n; + static blockJsonFallback = blocki18n; /** * Load the schema from the specified URL, with a fallback URL if needed. @@ -56,12 +54,11 @@ export class JsonSchemaExtractor { return fallback; } - console.log("Schema loaded successfully"); JsonSchemaExtractor.schemaCache[url] = response; return response; } catch (error) { console.error( - `\nFailed to load schema from ${url}. Using fallback. Error: ${error.message}`, + `\nFailed to load schema from ${url}. Using fallback. Error: ${(error as Error).message}`, ); JsonSchemaExtractor.schemaCache[url] = fallback; return fallback; @@ -134,7 +131,7 @@ export class JsonSchemaExtractor { }, ): Block[] | undefined { const { filename = "block.json", addReferences = false } = options; - const translations = []; + const translations: Block[] = []; /** * Recursive function to extract translatable strings @@ -161,7 +158,7 @@ export class JsonSchemaExtractor { // It's a string - add it to translations addTranslation( currentJson[key], - currentSchema[key], + currentSchema[key] as string, filename, addReferences, ); @@ -179,6 +176,7 @@ export class JsonSchemaExtractor { ); } else if ( typeof currentJson[key] === "object" && + currentJson[key] !== null && typeof currentSchema[key] === "object" ) { // It's an object - recurse @@ -191,18 +189,18 @@ export class JsonSchemaExtractor { /** * Handles arrays in JSON and schema - * @param {Array} jsonArray - The JSON array - * @param {Array} schemaArray - The schema array - * @param {Array} path - The current path + * @param {unknown[]} jsonArray - The JSON array + * @param {string[] | I18nSchema[]} schemaArray - The schema array + * @param {string[]} path - The current path * @param {string} filename - The name of the file * @param {boolean} addReferences - whenever to add references */ function handleArrays( - jsonArray, - schemaArray, - path, - filename, - addReferences, + jsonArray: unknown[], + schemaArray: string[] | I18nSchema[], + path: string[], + filename: string, + addReferences: boolean, ) { // If the schema has at least one element, use it as a template if (schemaArray.length > 0) { @@ -243,7 +241,7 @@ export class JsonSchemaExtractor { * @param {string} filename - The name of the file for references * @param {boolean} addReferences - Whether to add references */ - function addTranslation(msgctxt, msgid, filename, addReferences) { + function addTranslation(msgctxt: string, msgid: string, filename: string, addReferences: boolean) { if (!msgctxt) return; // Do not add empty strings const translation = { diff --git a/src/fs/glob.ts b/src/fs/glob.ts index 3d256e4..682c836 100644 --- a/src/fs/glob.ts +++ b/src/fs/glob.ts @@ -1,14 +1,13 @@ -import path from "node:path"; -import { Glob, type Path } from "glob"; -import { minimatch } from "minimatch"; +import path from 'node:path' +import { Glob, type Path } from 'glob' +import { minimatch } from 'minimatch' +import * as javascript from 'tree-sitter-javascript' // @ts-expect-error -import * as javascript from "tree-sitter-javascript"; +import * as php from 'tree-sitter-php' // @ts-expect-error -import * as php from "tree-sitter-php"; -// @ts-expect-error -import * as ts from "tree-sitter-typescript"; -import type { Args, Patterns } from "../types.js"; -import { detectPatternType, getFileExtension } from "../utils/common.js"; +import * as ts from 'tree-sitter-typescript' +import type { Args, Patterns } from '../types.js' +import { detectPatternType, getFileExtension } from '../utils/common.js' /** * Return the parser based on the file extension diff --git a/src/parser/exec.ts b/src/parser/exec.ts index 9454bbf..c56bd88 100644 --- a/src/parser/exec.ts +++ b/src/parser/exec.ts @@ -67,12 +67,12 @@ export async function exec(args: Args): Promise { audit(args, translationsUnion); } - /** generate the json file based on the --json flag passed */ + /** generate the JSON file based on the --json flag passed */ if (args.options?.json) { return outputJson(args, potHeader, translationsUnion); } - /** Generate the pot file json */ + /** Generate the pot file JSON */ const getTextTranslations: GetTextTranslations = { charset: getEncodingCharset(args.options?.charset), headers: potHeader as { [headerName: string]: string }, diff --git a/src/parser/makePot.ts b/src/parser/makePot.ts index 371273d..8da1ff2 100644 --- a/src/parser/makePot.ts +++ b/src/parser/makePot.ts @@ -11,7 +11,7 @@ import { exec } from "./exec.js"; * @return {string} - a promise that resolves when the pot file is generated */ export async function makePot(args: Args): Promise { - /** Collect metadata from the get package json */ + /** Collect metadata from the get-go package JSON */ const pkgData = extractPackageJson(args); /** Get metadata from the main file (theme and plugin) */ diff --git a/src/parser/progress.ts b/src/parser/progress.ts index 7f79819..9b995f7 100644 --- a/src/parser/progress.ts +++ b/src/parser/progress.ts @@ -4,11 +4,11 @@ import type { Args } from "../types.js"; /** * Initializes a progress bar and returns the progress bar element. * - * @param {Args} args - The argument object containing the source directory and other options. - * @param {number} filesCount - An array of file names. + * @param {Args} _args - The argument object containing the source directory and other options. + * @param {number} _filesCount - An array of file names. * @return {cliProgress.SingleBar} The progress bar element. */ -export function initProgress(args: Args, filesCount: number): SingleBar { +export function initProgress(_args: Args, _filesCount: number): SingleBar { // Set up the progress bar return new cliProgress.SingleBar( { diff --git a/src/utils/common.ts b/src/utils/common.ts index e6469d2..6d6b303 100644 --- a/src/utils/common.ts +++ b/src/utils/common.ts @@ -163,8 +163,7 @@ export function printTimeElapsed( timeEnd: Date = new Date(), ) { console.log( - `\nšŸš€ ${scriptName}: Task completed! ${scriptName.split("-")[1]} file created in ${ - timeEnd.getTime() - timeStart.getTime() + `\nšŸš€ ${scriptName}: Task completed! ${scriptName.split("-")[1]} file created in ${timeEnd.getTime() - timeStart.getTime() }ms`, ); } diff --git a/src/utils/extractors.ts b/src/utils/extractors.ts index b16d73d..2697f35 100644 --- a/src/utils/extractors.ts +++ b/src/utils/extractors.ts @@ -45,7 +45,7 @@ export const buildBlock = ( * Extracts strings from parsed JSON data. * * @param {Record | Parser.SyntaxNode} parsed - The parsed JSON data or syntax node. - * @param {string | Parser} filename - The filename or parser. + * @param {string | Parser} _filename - The filename or parser. * @param filepath - the path to the file being parsed * @return {SetOfBlocks} An array of translation strings. */ From c33eabc578d9ee122e7770afd34d493db7a548fd Mon Sep 17 00:00:00 2001 From: Erik Golinelli Date: Thu, 12 Feb 2026 18:34:21 +0100 Subject: [PATCH 04/12] Improve CLI header parsing with stricter typing and validation --- src/cli/parseCli.ts | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/src/cli/parseCli.ts b/src/cli/parseCli.ts index 98c9625..92d1550 100644 --- a/src/cli/parseCli.ts +++ b/src/cli/parseCli.ts @@ -102,10 +102,16 @@ export function parseCliArgs( } // Collect the headers passed via cli - const headers = {}; - for (const header of args.headers) { - const [key, value] = header.split(":") as Record; - headers[key.trim()] = value.trim(); + const headers: Record = {}; + if (args.headers && Array.isArray(args.headers)) { + for (const header of args.headers) { + if (typeof header === "string") { + const [key, value] = header.split(":") as [PotHeaders, string]; + if (key && value) { + headers[key.trim()] = value.trim(); + } + } + } } const parsedArgs: Args = { From 16bf20d658f8b0042edd299d7f1c7c343d58d65e Mon Sep 17 00:00:00 2001 From: Erik Golinelli Date: Thu, 12 Feb 2026 18:35:38 +0100 Subject: [PATCH 05/12] Refactor `parseCli` to improve handling of `scriptName` with consistent variable naming and safer array operations --- src/cli/parseCli.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/cli/parseCli.ts b/src/cli/parseCli.ts index 92d1550..128eeb9 100644 --- a/src/cli/parseCli.ts +++ b/src/cli/parseCli.ts @@ -177,9 +177,9 @@ export function parseJsonArgs( let scriptName: string | undefined; if (args.scriptName) { - scriptName = args.scriptName.split(",").map((s) => s.trim()); - if (scriptName.length === 1) { - scriptName = scriptName[0]; + const scripts = args.scriptName.toString().split(",").map((s) => s.trim()); + if (scripts.length === 1) { + scriptName = scripts[0]; } } From e22e03133ea0ac9f95998a2bbc7ff854ee0b80b0 Mon Sep 17 00:00:00 2001 From: Erik Golinelli Date: Thu, 12 Feb 2026 18:35:47 +0100 Subject: [PATCH 06/12] Refactor `JsonSchemaExtractor` for stricter typing, improved readability, and enhanced error handling --- src/extractors/schema.ts | 41 ++++++++++++++++++++++++++++------------ 1 file changed, 29 insertions(+), 12 deletions(-) diff --git a/src/extractors/schema.ts b/src/extractors/schema.ts index 2510d7c..10e4dae 100644 --- a/src/extractors/schema.ts +++ b/src/extractors/schema.ts @@ -6,6 +6,7 @@ import type { I18nSchema } from "../types.js"; /** * Extracts strings from JSON files using the I18n schema. */ +// biome-ignore lint/complexity/noStaticOnlyClass: export class JsonSchemaExtractor { private static schemaCache: { [url: string]: I18nSchema } = {}; @@ -34,10 +35,7 @@ export class JsonSchemaExtractor { } try { - console.log(`\n[i] Loading schema from ${url}`); const response = await fetch(url, { - responseType: "json", - accept: "application/json", headers: { "Access-Control-Allow-Origin": "*", }, @@ -107,7 +105,7 @@ export class JsonSchemaExtractor { options, ); } catch (error) { - console.error(`Error parsing JSON: ${error.message}`); + console.error(`Error parsing JSON: ${(error as Error).message}`); return; } } @@ -139,7 +137,11 @@ export class JsonSchemaExtractor { * @param {*} currentSchema - The current node in the schema * @param {Array} path - The current path in the JSON */ - function extract(currentJson, currentSchema, path = []) { + function extract( + currentJson: Record, + currentSchema: I18nSchema, + path: string[] = [], + ) { // If either is null or undefined, there's nothing to do if (!currentJson || !currentSchema) return; @@ -180,7 +182,11 @@ export class JsonSchemaExtractor { typeof currentSchema[key] === "object" ) { // It's an object - recurse - extract(currentJson[key], currentSchema[key], [...path, key]); + extract( + currentJson[key] as Record, + currentSchema[key] as I18nSchema, + [...path, key], + ); } } } @@ -210,19 +216,30 @@ export class JsonSchemaExtractor { for (const jsonItem of jsonArray) { if (typeof jsonItem === "string") { // If the JSON element is a string, add it directly - addTranslation(jsonItem, schemaTemplate, filename, addReferences); - } else if (typeof jsonItem === "object") { + addTranslation( + jsonItem, + schemaTemplate as string, + filename, + addReferences, + ); + } else if (typeof jsonItem === "object" && jsonItem !== null) { // If it's an object, recurse if (typeof schemaTemplate === "object") { - extract(jsonItem, schemaTemplate, path); + extract( + jsonItem as Record, + schemaTemplate as I18nSchema, + path, + ); } else { // Edge case: handles cases like keywords: ["string1", "string2"] // when the schema has keywords: ["keyword context"] for (const key of Object.keys(jsonItem)) { - if (typeof jsonItem[key] === "string") { + // Type guard to ensure we are accessing a string + const value = (jsonItem as Record)[key]; + if (typeof value === "string") { addTranslation( - jsonItem[key], - schemaTemplate, + value, + schemaTemplate as string, filename, addReferences, ); From 7d9b455bd53f135e32f595773c3f13579591b392 Mon Sep 17 00:00:00 2001 From: Erik Golinelli Date: Thu, 12 Feb 2026 18:36:19 +0100 Subject: [PATCH 07/12] Refactor `taskRunner` for enhanced logging output --- src/parser/taskRunner.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/parser/taskRunner.ts b/src/parser/taskRunner.ts index 2a5f03f..545816c 100644 --- a/src/parser/taskRunner.ts +++ b/src/parser/taskRunner.ts @@ -18,7 +18,7 @@ export async function taskRunner( args: Args, progressBar: SingleBar, ) { - const messages = []; + const messages: string[] = []; await Promise.allSettled(tasks) .then((strings) => { /** @@ -37,11 +37,12 @@ export async function taskRunner( * Add the strings to the destination set */ destination.addArray(result.blocks); + const strings = result.blocks.map((b) => b.msgid); /* Log the results */ messages.push( - `āœ… ${result.path} [${result.blocks.map((b) => b.msgid).join(", ")}]`, + `āœ… ${result.path} - ${strings.length} strings found [${strings.join(", ")}]`, ); - } else messages.push(`āŒ ${result.path} has no strings`); + } else messages.push(`āŒ ${result.path} - has no strings`); } } }) From 6e6c8a7036bb1cb2a7af5f43e90e9bc2ae103fc2 Mon Sep 17 00:00:00 2001 From: Erik Golinelli Date: Thu, 12 Feb 2026 18:36:35 +0100 Subject: [PATCH 08/12] Refactor `makeJson` for stricter typing, improved readability, and safer Babel transformations --- src/parser/makeJson.ts | 35 ++++++++++++++++++++--------------- 1 file changed, 20 insertions(+), 15 deletions(-) diff --git a/src/parser/makeJson.ts b/src/parser/makeJson.ts index ec60416..36a7108 100644 --- a/src/parser/makeJson.ts +++ b/src/parser/makeJson.ts @@ -1,7 +1,7 @@ import crypto from "node:crypto"; import * as fs from "node:fs"; import path from "node:path"; -import { transformSync } from "@babel/core"; +import { type NodePath, transformSync, type types as BabelTypes } from "@babel/core"; import type { SetOfBlocks } from "gettext-merger"; import { type GetTextTranslation, @@ -432,29 +432,34 @@ export class MakeJsonCommand { plugins: [ ({ types: t }) => ({ visitor: { - CallExpression(path) { + CallExpression(path: NodePath) { const callee = path.node.callee; // Check for pattern like: (fn)("...") - if ( - t.isSequenceExpression(callee) && - t.isMemberExpression(callee.expressions[1]) - ) { - const property = callee.expressions[1].property; - - if ( - t.isIdentifier(property) && - allowedFunctions.has(property.name) - ) { - // Replace with direct function call: __("..."), _n(...), etc. - path.node.callee = t.identifier(property.name); + if (t.isSequenceExpression(callee)) { + const seqExpr = callee as BabelTypes.SequenceExpression; + const secondExpression = seqExpr.expressions[1]; + + if (t.isMemberExpression(secondExpression)) { + const memberExpr = secondExpression as BabelTypes.MemberExpression; + const property = memberExpr.property; + + if (t.isIdentifier(property)) { + // Cast to Identifier + const identifier = property as BabelTypes.Identifier; + + if (allowedFunctions.has(identifier.name)) { + // Replace with direct function call: __("..."), _n(...), etc. + path.node.callee = t.identifier(identifier.name); + } + } } } }, }, }), ], - }).code as string; + })?.code ?? ''; return doTree(transformedScript, script, this.debug); } From 96ce09156984c938671158fdd3d1ef14ad0d2a35 Mon Sep 17 00:00:00 2001 From: Erik Golinelli Date: Thu, 12 Feb 2026 18:36:57 +0100 Subject: [PATCH 09/12] Refactor `output` for stricter typing and improved readability in JSON generation logic --- src/utils/output.ts | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/utils/output.ts b/src/utils/output.ts index 31f0195..826bacc 100644 --- a/src/utils/output.ts +++ b/src/utils/output.ts @@ -1,6 +1,7 @@ import type { SetOfBlocks } from "gettext-merger"; import Tannin from "tannin"; import type { Args } from "../types.js"; +import type { GetTextTranslation } from 'gettext-parser' /** * Outputs the pot file in json format based on the command line arguments --json option @@ -14,13 +15,15 @@ export function outputJson( args: Args, potHeader: Record | null, translationsUnion: SetOfBlocks, -) { - const jedData: { - [p: string]: { [p: string]: [string, string] }; - } = { +): string { + const jedData = { [args.slug]: { "": potHeader ?? {}, - ...(translationsUnion.toJson() as { [p: string]: [string, string] }), + ...(translationsUnion.toJson() as{ + [key: string]: { + [key: string]: GetTextTranslation; + }; + }), }, }; const i18n = new Tannin(jedData); From 76fa02dfb0905d7301f6801f4b18455724e9bc67 Mon Sep 17 00:00:00 2001 From: Erik Golinelli Date: Thu, 12 Feb 2026 18:37:11 +0100 Subject: [PATCH 10/12] Refactor `types`, `fs`, and `extractors` for stricter typing, improved readability, and safer handling of undefined values --- src/fs/fs.ts | 3 ++- src/types.ts | 7 +++++++ src/utils/extractors.ts | 6 +++--- 3 files changed, 12 insertions(+), 4 deletions(-) diff --git a/src/fs/fs.ts b/src/fs/fs.ts index 876c291..f5623fb 100644 --- a/src/fs/fs.ts +++ b/src/fs/fs.ts @@ -69,7 +69,8 @@ export function getEncodingCharset(charset: string | undefined): string { * @param args - the command line arguments */ function getOutputFilePath(args: Args): string { - const { out, headers, options } = args.paths; + const { paths, headers, options } = args; + const out = paths.out let i18nFolder = out ?? headers?.domainPath ?? "languages"; // Remove leading and trailing slashes diff --git a/src/types.ts b/src/types.ts index 7796601..e3fc3a1 100644 --- a/src/types.ts +++ b/src/types.ts @@ -103,6 +103,7 @@ export interface Args { packageName?: string; headers: { [key in PotHeaders]: string }; output?: boolean; + theme?: boolean; fileComment?: string; charset?: string; skip: { @@ -206,3 +207,9 @@ export interface I18nHeaders { slug: string; email: string | undefined; } + +export interface AuthorData { + name: string; + email?: string; + website?: string; +} diff --git a/src/utils/extractors.ts b/src/utils/extractors.ts index 2697f35..4904c15 100644 --- a/src/utils/extractors.ts +++ b/src/utils/extractors.ts @@ -50,13 +50,13 @@ export const buildBlock = ( * @return {SetOfBlocks} An array of translation strings. */ export function yieldParsedData( - parsed: Block[], - filename: "block.json" | "theme.json", + parsed: Block[] | undefined, + _filename: "block.json" | "theme.json", filepath: string, ): SetOfBlocks { const gettextTranslations: SetOfBlocks = new SetOfBlocks([], filepath); - if (parsed.length === 0) { + if (!parsed) { return gettextTranslations; } From 55ee9c6437aec7992af39625f6b2f3b7706ab42c Mon Sep 17 00:00:00 2001 From: Erik Golinelli Date: Thu, 12 Feb 2026 18:37:26 +0100 Subject: [PATCH 11/12] Add tests for `getAuthorFromPackage` to validate author extraction logic --- tests/extract-headers.test.js | 70 ++++++++++++++++++++++++++++++++++- 1 file changed, 69 insertions(+), 1 deletion(-) diff --git a/tests/extract-headers.test.js b/tests/extract-headers.test.js index f00ca98..d1a240d 100644 --- a/tests/extract-headers.test.js +++ b/tests/extract-headers.test.js @@ -1,7 +1,7 @@ const { describe, it } = require("node:test"); const { join } = require("node:path"); const assert = require("node:assert"); -const { extractMainFileData } = require("../lib"); +const { extractMainFileData, getAuthorFromPackage } = require("../lib"); describe("should parse plugin main file", () => { describe("should parse plugin.php", () => { @@ -52,3 +52,71 @@ describe("should parse theme main file", () => { }); }); }); + +describe("getAuthorFromPackage", () => { + it("extracts author from string with name and email", () => { + const pkgJson = { + author: "My Name ", + }; + const author = getAuthorFromPackage(pkgJson); + assert.deepStrictEqual(author, { + name: "My Name", + email: "myname@example.com", + website: undefined, + }); + }); + + it("extracts author from string with specific user format", () => { + const pkgJson = { + author: "my name ", + }; + const author = getAuthorFromPackage(pkgJson); + assert.deepStrictEqual(author, { + name: "my name", + email: "myname@asdasdasdasd.it", + website: undefined, + }); + }); + + it("extracts author from string with name, email and url", () => { + const pkgJson = { + author: "My Name (https://example.com)", + }; + const author = getAuthorFromPackage(pkgJson); + assert.deepStrictEqual(author, { + name: "My Name", + email: "myname@example.com", + website: "https://example.com", + }); + }); + + it("extracts author from object", () => { + const pkgJson = { + author: { + name: "Object Author", + email: "obj@example.com", + website: "https://obj.example.com" + } + }; + const author = getAuthorFromPackage(pkgJson); + assert.deepStrictEqual(author, { + name: "Object Author", + email: "obj@example.com", + website: "https://obj.example.com", + }); + }); + + it("extracts author from array of strings", () => { + const pkgJson = { + authors: [ + "Array Author " + ] + }; + const author = getAuthorFromPackage(pkgJson); + assert.deepStrictEqual(author, { + name: "Array Author", + email: "array@example.com", + website: undefined + }); + }); +}); From 8ac9a11a44a17937c429111c1efa17bbaa832570 Mon Sep 17 00:00:00 2001 From: Erik Golinelli Date: Thu, 12 Feb 2026 18:37:35 +0100 Subject: [PATCH 12/12] Update CI workflow to include linting and type-checking steps before tests --- .github/workflows/node.js.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/node.js.yml b/.github/workflows/node.js.yml index 37c5d12..dfc99ab 100644 --- a/.github/workflows/node.js.yml +++ b/.github/workflows/node.js.yml @@ -25,4 +25,6 @@ jobs: node-version: 22.x cache: 'npm' - run: npm install + - run: npm run lint + - run: npm run type-check - run: npm run test:ci