From 9e625973872e49f6dfa94408791023990bb10463 Mon Sep 17 00:00:00 2001 From: zhang17-24 <2201218482@qq.com> Date: Thu, 6 Aug 2026 01:13:37 +0800 Subject: [PATCH] fix(coding-agent): enabled multiline matching in ts-no-tiny-functions condition --- packages/coding-agent/CHANGELOG.md | 4 ++ .../builtin-rules/ts-no-tiny-functions.md | 2 +- .../test/discovery/builtin-defaults.test.ts | 51 +++++++++++++++++++ 3 files changed, 56 insertions(+), 1 deletion(-) diff --git a/packages/coding-agent/CHANGELOG.md b/packages/coding-agent/CHANGELOG.md index 456e88609..43a085c85 100644 --- a/packages/coding-agent/CHANGELOG.md +++ b/packages/coding-agent/CHANGELOG.md @@ -2,6 +2,10 @@ ## [Unreleased] +### Fixed + +- Fixed the bundled `ts-no-tiny-functions` TTSR rule never firing on one-line arrow functions in real files: the second alternative's `$` anchor only matched at the absolute end of input, so the trailing newline present in every real file suppressed the match. The condition now opens with the `(?m)` inline flag so the arrow body matches to the line end ([#6890](https://github.com/can1357/oh-my-pi/issues/6890)). + ## [17.2.9] - 2026-08-05 ### Breaking Changes diff --git a/packages/coding-agent/src/discovery/builtin-rules/ts-no-tiny-functions.md b/packages/coding-agent/src/discovery/builtin-rules/ts-no-tiny-functions.md index f6477a6be..b6b049a00 100644 --- a/packages/coding-agent/src/discovery/builtin-rules/ts-no-tiny-functions.md +++ b/packages/coding-agent/src/discovery/builtin-rules/ts-no-tiny-functions.md @@ -1,6 +1,6 @@ --- description: "Do not extract 1-2 line functions that only wrap an expression — inline them" -condition: "\\{\\s*return [^;{}\\n]+;?\\s*\\}|\\b(?:const|let|var)\\s+[\\w$]+\\s*=\\s*(\\([^)]*\\)|[a-zA-Z_$][\\w$]*)\\s*=>\\s*[^{\\n]+$" +condition: "(?m)\\{\\s*return [^;{}\\n]+;?\\s*\\}|\\b(?:const|let|var)\\s+[\\w$]+\\s*=\\s*(\\([^)]*\\)|[a-zA-Z_$][\\w$]*)\\s*=>\\s*[^{\\n]+$" scope: "tool:edit(*.ts), tool:edit(*.tsx), tool:write(*.ts), tool:write(*.tsx)" interruptMode: never --- diff --git a/packages/coding-agent/test/discovery/builtin-defaults.test.ts b/packages/coding-agent/test/discovery/builtin-defaults.test.ts index d67322aae..3bf97344a 100644 --- a/packages/coding-agent/test/discovery/builtin-defaults.test.ts +++ b/packages/coding-agent/test/discovery/builtin-defaults.test.ts @@ -181,6 +181,57 @@ describe("builtin-defaults rule provider", () => { }), ).toEqual([]); }); + it("opens every bundled regex condition that uses a bare line anchor with a translatable inline flag", async () => { + // Without the (?m)/(?s) inline flags a bare ^ or $ anchors to the + // absolute start/end of input, so a rule whose condition is anchored to + // a line silently stops matching in real files (see #6890). Enforce the + // pairing at load time so the failure class stays closed. + const rules = await loadBuiltinRules(); + for (const rule of rules) { + for (const condition of rule.condition ?? []) { + const outsideCharClasses = condition.replace(/\[[^\]]*\]/g, ""); + const hasBareAnchor = /(^|[^\\])[\^$]/.test(outsideCharClasses); + const opensWithTranslatableFlag = /^\(\?[ims]+\)/.test(condition); + expect( + hasBareAnchor ? opensWithTranslatableFlag : true, + `${rule.name}: a condition with a bare ^ or $ anchor must open with a translatable inline flag`, + ).toBe(true); + } + } + }); + + it("fires ts-no-tiny-functions on one-line arrow functions even with a trailing newline", async () => { + const rules = await loadBuiltinRules(); + const rule = rules.find(r => r.name === "ts-no-tiny-functions"); + if (!rule) throw new Error("ts-no-tiny-functions rule missing"); + + const manager = new TtsrManager(); + expect(manager.addRule(rule)).toBe(true); + const ctx: TtsrMatchContext = { source: "tool", toolName: "edit", filePaths: ["src/foo.ts"] }; + + // Real files end with a newline, so the arrow alternative must match + // before the line terminator, not only at the absolute end of input. + const hits = [ + "const getName = (u) => u.profile.name;\n", + "const getName = (u) => u.profile.name;", + "const a = 1;\nconst getName = (u) => u.profile.name;\nconst b = 2;", + ]; + for (const snippet of hits) { + manager.resetBuffer(); + expect( + manager.checkDelta(snippet, ctx).map(m => m.name), + snippet, + ).toEqual(["ts-no-tiny-functions"]); + } + + // Multi-statement functions (block bodies) are not tiny wrappers. + const misses = ["function f(v) { const x = v.a; return x; }", "const f = (v) => { const x = v.a; return x; };"]; + for (const snippet of misses) { + manager.resetBuffer(); + expect(manager.checkDelta(snippet, ctx), snippet).toEqual([]); + } + }); + it("go-new-expr matches value→pointer helpers (named + generic) but not real functions, only on *.go", async () => { const rules = await loadBuiltinRules(); const rule = rules.find(r => r.name === "go-new-expr");