From 4c680d099c286d267fc1bb76aad81c1ee23f130c Mon Sep 17 00:00:00 2001 From: morluto <76467478+morluto@users.noreply.github.com> Date: Sun, 25 Oct 2026 10:05:59 +0800 Subject: [PATCH] fix(lint): preserve portable advertised patterns through autofix --- .oxlintrc.json | 1 + docs/testing.md | 4 ++ src/domain/adb/adbDeviceAnalysis.ts | 2 +- .../filesystem/lintRuleConfiguration.test.ts | 41 ++++++++++++++++++- 4 files changed, 46 insertions(+), 2 deletions(-) diff --git a/.oxlintrc.json b/.oxlintrc.json index d41674db8a..db7907b215 100644 --- a/.oxlintrc.json +++ b/.oxlintrc.json @@ -7,6 +7,7 @@ "no-async-promise-executor": "error", "no-unreachable": "error", "no-unsafe-optional-chaining": "error", + "no-useless-escape": ["warn", { "allowRegexCharacters": ["-"] }], "complexity": ["warn", 20], "max-depth": ["warn", 4], "max-lines": [ diff --git a/docs/testing.md b/docs/testing.md index 5a6fd931a0..36544c0cf6 100644 --- a/docs/testing.md +++ b/docs/testing.md @@ -178,6 +178,10 @@ directives are errors. `vitest/valid-expect` warns (`maxArgs: 2`) because valid collected assertions can be awaited later. `import/no-duplicates` warns with `preferInline: false`. +`no-useless-escape` keeps intentional `\-` regex escapes because advertised +patterns must also compile in `v` mode; the catalog regression checks every +advertised pattern. + Fast lint stays syntax-based. `npm run lint:typed` runs serially with pinned `oxlint-tsgolint` 7.0.2005 and records native plus typed diagnostics in `.cache/static-checks/typed-oxlint.json`. Production `src` treats diff --git a/src/domain/adb/adbDeviceAnalysis.ts b/src/domain/adb/adbDeviceAnalysis.ts index 4110a9ec9e..740e6d41bd 100644 --- a/src/domain/adb/adbDeviceAnalysis.ts +++ b/src/domain/adb/adbDeviceAnalysis.ts @@ -64,7 +64,7 @@ export const MAX_ADB_ACTIVITY_EXTRAS = 16; // Android's `am start --ef` parses this text with Float.valueOf. Keep the // caller's spelling intact while admitting Java FloatValue forms. const javaFloatValuePattern = - /^[\x01-\x20]*[+-]?(?:NaN|Infinity|(?:(?:\d+(?:\.\d*)?|\.\d+)(?:[eE][+-]?\d+)?[fFdD]?|0[xX](?:[\da-fA-F]+(?:\.[\da-fA-F]*)?|\.[\da-fA-F]+)[pP][+-]?\d+[fFdD]?))[\x01-\x20]*$/u; + /^[\x01-\x20]*(?:\+|-)?(?:NaN|Infinity|(?:(?:\d+(?:\.\d*)?|\.\d+)(?:[eE](?:\+|-)?\d+)?[fFdD]?|0[xX](?:[\da-fA-F]+(?:\.[\da-fA-F]*)?|\.[\da-fA-F]+)[pP](?:\+|-)?\d+[fFdD]?))[\x01-\x20]*$/u; const activityExtraSchema = z.discriminatedUnion("type", [ z.strictObject({ key: activityExtraKeySchema, diff --git a/tests/boundary/filesystem/lintRuleConfiguration.test.ts b/tests/boundary/filesystem/lintRuleConfiguration.test.ts index fb5dc363cf..e4f2fcafeb 100644 --- a/tests/boundary/filesystem/lintRuleConfiguration.test.ts +++ b/tests/boundary/filesystem/lintRuleConfiguration.test.ts @@ -1,5 +1,5 @@ import { parse } from "@babel/parser"; -import { mkdir, writeFile } from "node:fs/promises"; +import { mkdir, readFile, writeFile } from "node:fs/promises"; import { dirname, isAbsolute, join, relative, resolve, sep } from "node:path"; import { fileURLToPath, pathToFileURL } from "node:url"; import { expect } from "vitest"; @@ -268,3 +268,42 @@ for (const scenario of cases) { } }); } + +processTest( + "preserves portable regex escapes while fixing unnecessary ones", + async ({ processes }) => { + const project = await createTestTempDirectory("rea-lint-regex-fix-"); + const configuration = await writeRepositoryOxlintConfiguration(project); + const path = join(project, applicationSource); + await mkdir(dirname(path), { recursive: true }); + await writeFile( + path, + String.raw`export const intentional = /[+\-]/u; +export const unnecessary = /\!/; +`, + ); + + const result = await processes.run( + process.execPath, + [ + oxlint, + "--config", + configuration, + "--threads", + "1", + "--fix", + applicationSource, + ], + { cwd: project, timeoutMs: 15_000 }, + ); + expect(result.signal, result.stderr).toBeNull(); + expect(result.exitCode, result.stderr || result.stdout).toBe(0); + + const fixed = await readFile(path, "utf8"); + expect(fixed).toContain(String.raw`export const intentional = /[+\-]/u;`); + expect(fixed).toContain("export const unnecessary = /!/;"); + const advertised = /intentional\s*=\s*\/(.+)\/u/u.exec(fixed)?.[1]; + if (advertised === undefined) throw new Error("Fixed regex was not found"); + expect(() => new RegExp(advertised, "v")).not.toThrow(); + }, +);