|
5 | 5 | "use strict"
|
6 | 6 |
|
7 | 7 | const { RuleTester, TsRuleTester } = require("#test-helpers")
|
| 8 | +const Module = require("node:module") |
| 9 | +const assert = require("node:assert") |
8 | 10 | const rule = require("../../../lib/rules/no-sync")
|
| 11 | +const sinon = require("sinon") |
9 | 12 |
|
10 | 13 | new RuleTester().run("no-sync", rule, {
|
11 | 14 | valid: [
|
@@ -319,3 +322,162 @@ fooSync();
|
319 | 322 | },
|
320 | 323 | ],
|
321 | 324 | })
|
| 325 | + |
| 326 | +describe("no-sync rule with missing dependencies", () => { |
| 327 | + const originalRequire = module.require |
| 328 | + let mockRequire |
| 329 | + let originalModules = {} |
| 330 | + |
| 331 | + /** |
| 332 | + * Helper function to mock a module. |
| 333 | + * - Assigns a module object to `require.cache` with the provided mock exports to prevent TypeScript errors when the module is required, as TypeScript expects the module to exist in the cache with an 'exports' property. |
| 334 | + * |
| 335 | + * @param {string} modulePath - The path to the module. |
| 336 | + * @param {*} mockExports - The mock exports to use. |
| 337 | + */ |
| 338 | + function mockModule(modulePath, mockExports) { |
| 339 | + const resolvedPath = require.resolve(modulePath) |
| 340 | + |
| 341 | + // Store original module if not already stored. |
| 342 | + if (!originalModules[resolvedPath] && require.cache[resolvedPath]) { |
| 343 | + originalModules[resolvedPath] = require.cache[resolvedPath] |
| 344 | + } |
| 345 | + |
| 346 | + require.cache[resolvedPath] = { exports: mockExports } |
| 347 | + } |
| 348 | + |
| 349 | + /** |
| 350 | + * Helper to test rule behavior with missing dependencies. |
| 351 | + * - Sets mocks for each dependency based on options. |
| 352 | + * |
| 353 | + * @param {object} options - Test options. |
| 354 | + * @param {boolean} options.mockTsDeclarationLocation - Whether to mock `ts-declaration-location` as missing. |
| 355 | + * @param {boolean} options.mockTypeScriptServices - Whether to mock TypeScript services as missing. |
| 356 | + * @param {RegExp} options.expectedError - The expected error message pattern. |
| 357 | + */ |
| 358 | + function testWithMissingDependency(options) { |
| 359 | + const mockModules = {} |
| 360 | + |
| 361 | + if (options.mockTsDeclarationLocation) { |
| 362 | + mockModules["ts-declaration-location"] = { |
| 363 | + throws: new Error( |
| 364 | + "Cannot find module 'ts-declaration-location'" |
| 365 | + ), |
| 366 | + } |
| 367 | + } |
| 368 | + |
| 369 | + // Stub for the require function. |
| 370 | + mockRequire = sinon |
| 371 | + .stub(Module.prototype, "require") |
| 372 | + .callsFake(function (id) { |
| 373 | + if (mockModules[id] && mockModules[id].throws) { |
| 374 | + throw mockModules[id].throws |
| 375 | + } |
| 376 | + return originalRequire.apply(this, arguments) |
| 377 | + }) |
| 378 | + |
| 379 | + if (options.mockTypeScriptServices) { |
| 380 | + const mockGetParserServices = function () { |
| 381 | + return null |
| 382 | + } |
| 383 | + |
| 384 | + mockModule( |
| 385 | + "../../../lib/util/get-parser-services", |
| 386 | + mockGetParserServices |
| 387 | + ) |
| 388 | + } |
| 389 | + |
| 390 | + // Directly create and test the rule. |
| 391 | + const rule = require("../../../lib/rules/no-sync") |
| 392 | + |
| 393 | + // Context with required fields to satisfy TypeScript parser requirements. |
| 394 | + const context = { |
| 395 | + options: [ |
| 396 | + { |
| 397 | + ignores: [ |
| 398 | + { |
| 399 | + from: "file", |
| 400 | + }, |
| 401 | + ], |
| 402 | + }, |
| 403 | + ], |
| 404 | + } |
| 405 | + |
| 406 | + // Node that triggers the rule. |
| 407 | + const node = { |
| 408 | + name: "fooSync", |
| 409 | + } |
| 410 | + |
| 411 | + // Test if the rule throws the expected error. |
| 412 | + let errorThrown = false |
| 413 | + try { |
| 414 | + const ruleListener = rule.create(context) |
| 415 | + const selectors = Object.keys(ruleListener) |
| 416 | + |
| 417 | + for (const selector of selectors) { |
| 418 | + try { |
| 419 | + if (typeof ruleListener[selector] === "function") { |
| 420 | + ruleListener[selector](node) |
| 421 | + } |
| 422 | + } catch (e) { |
| 423 | + if (e.message.match(options.expectedError)) { |
| 424 | + errorThrown = true |
| 425 | + break |
| 426 | + } |
| 427 | + } |
| 428 | + } |
| 429 | + } catch (e) { |
| 430 | + if (e.message.match(options.expectedError)) { |
| 431 | + errorThrown = true |
| 432 | + } else { |
| 433 | + throw e |
| 434 | + } |
| 435 | + } |
| 436 | + |
| 437 | + assert.ok( |
| 438 | + errorThrown, |
| 439 | + `Expected error matching ${options.expectedError} was not thrown` |
| 440 | + ) |
| 441 | + } |
| 442 | + |
| 443 | + beforeEach(() => { |
| 444 | + delete require.cache[require.resolve("../../../lib/rules/no-sync")] |
| 445 | + delete require.cache[ |
| 446 | + require.resolve("../../../lib/util/get-parser-services") |
| 447 | + ] |
| 448 | + }) |
| 449 | + |
| 450 | + afterEach(() => { |
| 451 | + if (mockRequire && typeof mockRequire.restore === "function") { |
| 452 | + mockRequire.restore() |
| 453 | + } |
| 454 | + |
| 455 | + sinon.restore() |
| 456 | + module.require = originalRequire |
| 457 | + }) |
| 458 | + |
| 459 | + it("should throw if `ts-declaration-location` is not installed", function () { |
| 460 | + testWithMissingDependency({ |
| 461 | + mockTsDeclarationLocation: true, |
| 462 | + mockTypeScriptServices: false, |
| 463 | + expectedError: /ts-declaration-location not available/, |
| 464 | + }) |
| 465 | + }) |
| 466 | + |
| 467 | + it("should throw if TypeScript parser services are not available", function () { |
| 468 | + testWithMissingDependency({ |
| 469 | + mockTsDeclarationLocation: false, |
| 470 | + mockTypeScriptServices: true, |
| 471 | + expectedError: /TypeScript parser services not available/, |
| 472 | + }) |
| 473 | + }) |
| 474 | + |
| 475 | + it("should throw if both `ts-declaration-location` and `typescript` are not available", function () { |
| 476 | + testWithMissingDependency({ |
| 477 | + mockTsDeclarationLocation: true, |
| 478 | + mockTypeScriptServices: true, |
| 479 | + expectedError: |
| 480 | + /TypeScript parser services not available|ts-declaration-location not available/, |
| 481 | + }) |
| 482 | + }) |
| 483 | +}) |
0 commit comments