|
1 | 1 | import assert from "node:assert/strict" |
2 | 2 | import { describe, it } from "node:test" |
| 3 | +import { fileURLToPath, URL } from "node:url" |
3 | 4 |
|
4 | | -import { Linter } from "eslint" |
| 5 | +import { ESLint, Linter } from "eslint" |
5 | 6 | import typescriptEslint from "typescript-eslint" |
6 | 7 |
|
7 | 8 | import { apiProviderHygieneConfig, apiProviderTestSelectors, apiSpecTsIgnoreConfig } from "./api-provider-hygiene.js" |
@@ -173,6 +174,37 @@ describe("api provider hygiene: spec loops", () => { |
173 | 174 | assert.deepEqual(lintSpec(code), []) |
174 | 175 | }) |
175 | 176 | } |
| 177 | + |
| 178 | + for (const code of [ |
| 179 | + 'await expect(p).rejects.toThrow("aborted")', |
| 180 | + "expect(() => f()).toThrow('abort')", |
| 181 | + 'await expect(p).rejects.toThrowError("aborted")', |
| 182 | + "expect(() => f()).toThrow(/abort/i)", |
| 183 | + "await expect(p).rejects.toThrowError(/aborted/i)", |
| 184 | + 'await expect(p).rejects.toThrow("was aborted")', |
| 185 | + 'await expect(p).rejects.toThrow("Request was aborted.")', |
| 186 | + 'await expect(p).rejects.toThrow("The request was aborted")', |
| 187 | + "await expect(p).rejects.toThrow(/was aborted/)", |
| 188 | + ]) { |
| 189 | + it(`flags a bare abort substring: ${code}`, () => { |
| 190 | + assert.equal(lintSpec(code).length, 1) |
| 191 | + }) |
| 192 | + } |
| 193 | + |
| 194 | + for (const code of [ |
| 195 | + 'await expect(p).rejects.toThrow("The Requesty request was aborted")', |
| 196 | + 'await expect(p).rejects.toMatchObject({ name: "AbortError" })', |
| 197 | + 'await expect(p).rejects.toThrow("API Error")', |
| 198 | + 'expect(() => f()).not.toThrow("aborted")', |
| 199 | + 'await expect(p).rejects.not.toThrow("aborted")', |
| 200 | + "expect(() => f()).not.toThrowError(/abort/i)", |
| 201 | + "await expect(p).rejects.not.toThrowError(/aborted/i)", |
| 202 | + "await expect(p).rejects.toThrow(/^The Requesty request was aborted$/)", |
| 203 | + ]) { |
| 204 | + it(`allows: ${code}`, () => { |
| 205 | + assert.deepEqual(lintSpec(code), []) |
| 206 | + }) |
| 207 | + } |
176 | 208 | }) |
177 | 209 |
|
178 | 210 | describe("api provider hygiene: hand-built AbortError", () => { |
@@ -245,3 +277,68 @@ describe("api provider hygiene: @ts-ignore in specs", () => { |
245 | 277 | assert.deepEqual(lintSpecFile("// @ts-ignore x\nhandler.client", "services/__tests__/example.spec.ts"), []) |
246 | 278 | }) |
247 | 279 | }) |
| 280 | + |
| 281 | +// Resolve each lane through the extension's real config before exercising its syntax rules. |
| 282 | +// Isolating syntax rules avoids requiring fixture files in the TypeScript project. |
| 283 | +describe("abort assertions in the extension ESLint config", () => { |
| 284 | + const srcRoot = fileURLToPath(new URL("../../src/", import.meta.url)) |
| 285 | + const eslint = new ESLint({ cwd: srcRoot }) |
| 286 | + |
| 287 | + for (const filename of [ |
| 288 | + "core/task/__tests__/example.spec.ts", |
| 289 | + "core/config/__tests__/example.spec.ts", |
| 290 | + // The workflow tests are exempt from the path-traversal selectors, not from the abort |
| 291 | + // rule; listing one proves the lane that re-applies it actually resolves for them. |
| 292 | + "services/__tests__/pr-review-state-workflow.test.ts", |
| 293 | + ]) { |
| 294 | + it(`checks rejected and accepted assertions in ${filename}`, async () => { |
| 295 | + const resolved = await eslint.calculateConfigForFile(filename) |
| 296 | + const syntaxConfig = [ |
| 297 | + { |
| 298 | + files: ["**/*.ts"], |
| 299 | + languageOptions: { parser: typescriptEslint.parser }, |
| 300 | + rules: { "no-restricted-syntax": resolved.rules["no-restricted-syntax"] }, |
| 301 | + }, |
| 302 | + ] |
| 303 | + for (const [code, expectedCount] of [ |
| 304 | + ['await expect(p).rejects.toThrow("aborted")', 1], |
| 305 | + ["await expect(p).rejects.toThrowError(/abort/i)", 1], |
| 306 | + // An escaped dot in the source text is still a bare abort substring: the pattern |
| 307 | + // keeps a backslash, so the matcher must allow it before the trailing dot or anchor. |
| 308 | + ["await expect(p).rejects.toThrow(/^aborted$/)", 1], |
| 309 | + ["await expect(p).rejects.toThrow(/.*aborted.*/i)", 1], |
| 310 | + ["await expect(p).rejects.toThrowError(/abort(?:ed)/i)", 1], |
| 311 | + ["await expect(p).rejects.toThrow(/aborted\\b/i)", 1], |
| 312 | + ['await expect(p).rejects.toThrow("abort$")', 0], |
| 313 | + ['await expect(p).rejects.toThrow("^abort")', 0], |
| 314 | + ["await expect(p).rejects.toThrow(/.*The Requesty request was aborted.*/i)", 0], |
| 315 | + ["await expect(p).rejects.toThrow(/The Requesty request was aborted\\b/i)", 0], |
| 316 | + ["await expect(p).rejects.toThrow(/^Request was aborted\\.$/)", 1], |
| 317 | + ["await expect(p).rejects.toThrowError(/request aborted\\.$/i)", 1], |
| 318 | + ["await expect(p).rejects.toThrow(/^request aborted\\$/)", 1], |
| 319 | + // An inline non-capturing group is regex source, not part of the asserted |
| 320 | + // message: an anchored match that stops short of it lets a bare abort through. |
| 321 | + ["await expect(p).rejects.toThrow(/abort(?:ed)?/i)", 1], |
| 322 | + ["await expect(p).rejects.toThrowError(/^request aborted(?:ed)?$/i)", 1], |
| 323 | + ["await expect(p).rejects.toThrow(/cancel(?:ed)?/i)", 0], |
| 324 | + [ |
| 325 | + "await expect(p).rejects.toThrow(/^The Requesty request was aborted(?:ed)? because the peer closed the stream\\.$/)", |
| 326 | + 0, |
| 327 | + ], |
| 328 | + ['await expect(p).rejects.not.toThrow("aborted")', 0], |
| 329 | + ["await expect(p).rejects.toThrow(/^Request was aborted because the peer closed the stream\\.$/)", 0], |
| 330 | + [ |
| 331 | + 'await expect(p).rejects.toMatchObject({ name: "AbortError", message: "The Requesty request was aborted" })', |
| 332 | + 0, |
| 333 | + ], |
| 334 | + ]) { |
| 335 | + const messages = linter.verify(code, syntaxConfig, { filename }) |
| 336 | + assert.equal(messages.length, expectedCount, code) |
| 337 | + if (expectedCount) { |
| 338 | + assert.equal(messages[0].ruleId, "no-restricted-syntax") |
| 339 | + assert.match(messages[0].message, /bare 'abort' substring/) |
| 340 | + } |
| 341 | + } |
| 342 | + }) |
| 343 | + } |
| 344 | +}) |
0 commit comments