From c49d173a9947802073d8fa071c9240b73946eebc Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 19 Jul 2026 10:15:25 +0000 Subject: [PATCH 1/3] Initial plan From 419369fa8cc003459b2014bcec76393eae49c0b8 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 19 Jul 2026 10:25:45 +0000 Subject: [PATCH 2/3] Make unsafe prefer-core-logging rewrites report-only Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- .../src/rules/prefer-core-logging.test.ts | 30 +++++++++++- .../src/rules/prefer-core-logging.ts | 47 ++++++++++++++----- 2 files changed, 63 insertions(+), 14 deletions(-) diff --git a/eslint-factory/src/rules/prefer-core-logging.test.ts b/eslint-factory/src/rules/prefer-core-logging.test.ts index e5e2b0e7f61..759e9ae4a14 100644 --- a/eslint-factory/src/rules/prefer-core-logging.test.ts +++ b/eslint-factory/src/rules/prefer-core-logging.test.ts @@ -162,7 +162,35 @@ describe("prefer-core-logging", () => { { messageId: "preferCoreLogging", data: { method: "log", replacement: "core.info" }, - suggestions: [{ messageId: "replaceWithCoreMethod", data: { replacement: "core.info", args: `"value:", someVar` }, output: `const core = require("@actions/core"); const someVar = 1; core.info("value:", someVar);` }], + suggestions: [], + }, + ], + }, + ], + }); + }); + + it("invalid: console.log with format specifier is report-only", () => { + ruleTester.run("prefer-core-logging", preferCoreLoggingRule, { + valid: [], + invalid: [ + { + code: `const core = require("@actions/core"); console.log("%s processed");`, + errors: [ + { + messageId: "preferCoreLogging", + data: { method: "log", replacement: "core.info" }, + suggestions: [], + }, + ], + }, + { + code: `const core = require("@actions/core"); const someVar = 1; console.log("value: %s", someVar);`, + errors: [ + { + messageId: "preferCoreLogging", + data: { method: "log", replacement: "core.info" }, + suggestions: [], }, ], }, diff --git a/eslint-factory/src/rules/prefer-core-logging.ts b/eslint-factory/src/rules/prefer-core-logging.ts index a1b36612aec..1e98df2337c 100644 --- a/eslint-factory/src/rules/prefer-core-logging.ts +++ b/eslint-factory/src/rules/prefer-core-logging.ts @@ -1,4 +1,4 @@ -import { AST_NODE_TYPES, ESLintUtils, TSESTree } from "@typescript-eslint/utils"; +import { AST_NODE_TYPES, ESLintUtils, TSESLint, TSESTree } from "@typescript-eslint/utils"; const createRule = ESLintUtils.RuleCreator(name => `https://github.com/github/gh-aw/tree/main/eslint-factory#${name}`); @@ -30,6 +30,27 @@ function getConsoleMethod(node: TSESTree.CallExpression): string | null { return prop.name in CONSOLE_TO_CORE ? prop.name : null; } +function getStaticStringValue(node: TSESTree.CallExpressionArgument): string | null { + if (node.type === AST_NODE_TYPES.Literal) { + return typeof node.value === "string" ? node.value : null; + } + + if (node.type !== AST_NODE_TYPES.TemplateLiteral) { + return null; + } + + return node.quasis.map(quasi => quasi.value.cooked ?? "").join(""); +} + +function hasConsoleFormatSpecifier(node: TSESTree.CallExpressionArgument | undefined): boolean { + const value = node ? getStaticStringValue(node) : null; + return value !== null && /(^|[^%])%(?:[sdifoOjO])/.test(value); +} + +function canSuggestCoreReplacement(node: TSESTree.CallExpression): boolean { + return node.arguments.length === 1 && !hasConsoleFormatSpecifier(node.arguments[0]); +} + export const preferCoreLoggingRule = createRule({ name: "prefer-core-logging", meta: { @@ -57,23 +78,23 @@ export const preferCoreLoggingRule = createRule({ if (!method) return; const replacement = CONSOLE_TO_CORE[method]!; - - // Build replacement argument text from original call - const argsText = node.arguments.map(arg => sourceCode.getText(arg)).join(", "); + const suggest = canSuggestCoreReplacement(node) + ? [ + { + messageId: "replaceWithCoreMethod" as const, + data: { replacement, args: sourceCode.getText(node.arguments[0]) }, + fix(fixer: TSESLint.RuleFixer) { + return fixer.replaceText(node, `${replacement}(${sourceCode.getText(node.arguments[0])})`); + }, + }, + ] + : undefined; context.report({ node, messageId: "preferCoreLogging", data: { method, replacement }, - suggest: [ - { - messageId: "replaceWithCoreMethod", - data: { replacement, args: argsText }, - fix(fixer) { - return fixer.replaceText(node, `${replacement}(${argsText})`); - }, - }, - ], + suggest, }); }, }; From 72e18ebc2be9ec3612beb8f73bc21c03c7ba7b3d Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 19 Jul 2026 10:36:31 +0000 Subject: [PATCH 3/3] Prevent lossy prefer-core-logging suggestions Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- .../src/rules/prefer-core-logging.test.ts | 20 ++++++++++ .../src/rules/prefer-core-logging.ts | 37 +++++++++++++++++-- 2 files changed, 54 insertions(+), 3 deletions(-) diff --git a/eslint-factory/src/rules/prefer-core-logging.test.ts b/eslint-factory/src/rules/prefer-core-logging.test.ts index 759e9ae4a14..97390f99a43 100644 --- a/eslint-factory/src/rules/prefer-core-logging.test.ts +++ b/eslint-factory/src/rules/prefer-core-logging.test.ts @@ -38,6 +38,16 @@ describe("prefer-core-logging", () => { }, ], }, + { + code: "console.log(`hello`);", + errors: [ + { + messageId: "preferCoreLogging", + data: { method: "log", replacement: "core.info" }, + suggestions: [{ messageId: "replaceWithCoreMethod", data: { replacement: "core.info", args: "`hello`" }, output: "core.info(`hello`);" }], + }, + ], + }, ], }); }); @@ -194,6 +204,16 @@ describe("prefer-core-logging", () => { }, ], }, + { + code: 'const core = require("@actions/core"); const someVar = 1; console.log(`value: ${someVar}`);', + errors: [ + { + messageId: "preferCoreLogging", + data: { method: "log", replacement: "core.info" }, + suggestions: [], + }, + ], + }, ], }); }); diff --git a/eslint-factory/src/rules/prefer-core-logging.ts b/eslint-factory/src/rules/prefer-core-logging.ts index 1e98df2337c..ac372a83e9d 100644 --- a/eslint-factory/src/rules/prefer-core-logging.ts +++ b/eslint-factory/src/rules/prefer-core-logging.ts @@ -39,16 +39,47 @@ function getStaticStringValue(node: TSESTree.CallExpressionArgument): string | n return null; } - return node.quasis.map(quasi => quasi.value.cooked ?? "").join(""); + if (node.expressions.length > 0) { + return null; + } + + const parts: string[] = []; + for (const quasi of node.quasis) { + if (quasi.value.cooked === null) { + return null; + } + parts.push(quasi.value.cooked); + } + + return parts.join(""); } function hasConsoleFormatSpecifier(node: TSESTree.CallExpressionArgument | undefined): boolean { const value = node ? getStaticStringValue(node) : null; - return value !== null && /(^|[^%])%(?:[sdifoOjO])/.test(value); + if (value === null) { + return false; + } + + for (let i = 0; i < value.length - 1; i++) { + if (value[i] === "%" && value[i - 1] !== "%" && "sdifojO".includes(value[i + 1] ?? "")) { + return true; + } + } + + return false; +} + +function isInterpolatedTemplateLiteral(node: TSESTree.CallExpressionArgument): boolean { + return node.type === AST_NODE_TYPES.TemplateLiteral && node.expressions.length > 0; } function canSuggestCoreReplacement(node: TSESTree.CallExpression): boolean { - return node.arguments.length === 1 && !hasConsoleFormatSpecifier(node.arguments[0]); + const arg = node.arguments[0]; + if (node.arguments.length !== 1 || !arg) { + return false; + } + + return !isInterpolatedTemplateLiteral(arg) && !hasConsoleFormatSpecifier(arg); } export const preferCoreLoggingRule = createRule({