oxlint-plugin-react-doctor 0.7.9-dev.6325f9c → 0.7.9-dev.6c5c91b

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/index.js +3004 -480
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -579,6 +579,22 @@ const EXTERNAL_SYNC_OBSERVER_CONSTRUCTORS = new Set([
579
579
  "ResizeObserver",
580
580
  "PerformanceObserver"
581
581
  ]);
582
+ const EXTERNAL_SYNC_DOM_MEMBER_METHOD_NAMES = new Set([
583
+ "blur",
584
+ "focus",
585
+ "getBoundingClientRect",
586
+ "getClientRects",
587
+ "measure",
588
+ "measureInWindow",
589
+ "measureLayout",
590
+ "scroll",
591
+ "scrollBy",
592
+ "scrollIntoView",
593
+ "scrollTo",
594
+ "select",
595
+ "setRangeText",
596
+ "setSelectionRange"
597
+ ]);
582
598
  const STORAGE_OBJECTS = new Set(["localStorage", "sessionStorage"]);
583
599
  //#endregion
584
600
  //#region src/plugin/constants/react.ts
@@ -1749,7 +1765,7 @@ const isImportedFromModule = (contextNode, localIdentifierName, moduleSource) =>
1749
1765
  if (!info) return false;
1750
1766
  return info.source === moduleSource;
1751
1767
  };
1752
- const isNamespaceImportFromModule = (contextNode, localIdentifierName, moduleSource) => {
1768
+ const isNamespaceImportFromModule$1 = (contextNode, localIdentifierName, moduleSource) => {
1753
1769
  const lookup = getImportLookup(contextNode);
1754
1770
  if (!lookup) return false;
1755
1771
  const info = lookup.get(localIdentifierName);
@@ -1835,7 +1851,7 @@ const GENERATED_IMAGE_RENDERER_MODULES = [
1835
1851
  "satori"
1836
1852
  ];
1837
1853
  const IMAGE_RESPONSE_MODULES = new Set(["next/og", "@vercel/og"]);
1838
- const getImportDeclaration$1 = (node) => {
1854
+ const getImportDeclaration = (node) => {
1839
1855
  let current = node.parent;
1840
1856
  while (current) {
1841
1857
  if (isNodeOfType(current, "ImportDeclaration")) return current;
@@ -1849,7 +1865,7 @@ const isNamedImport = (symbol, importedName, moduleSources) => {
1849
1865
  if (symbol.kind !== "import") return false;
1850
1866
  const declaration = symbol.declarationNode;
1851
1867
  if (!isNodeOfType(declaration, "ImportSpecifier")) return false;
1852
- const importDeclaration = getImportDeclaration$1(declaration);
1868
+ const importDeclaration = getImportDeclaration(declaration);
1853
1869
  const source = importDeclaration ? getImportSource(importDeclaration) : null;
1854
1870
  if (!source || !moduleSources.has(source)) return false;
1855
1871
  const imported = declaration.imported;
@@ -1858,7 +1874,7 @@ const isNamedImport = (symbol, importedName, moduleSources) => {
1858
1874
  const isSatoriImport = (symbol) => {
1859
1875
  if (symbol.kind !== "import") return false;
1860
1876
  const declaration = symbol.declarationNode;
1861
- const importDeclaration = getImportDeclaration$1(declaration);
1877
+ const importDeclaration = getImportDeclaration(declaration);
1862
1878
  if (!importDeclaration || getImportSource(importDeclaration) !== "satori") return false;
1863
1879
  if (isNodeOfType(declaration, "ImportDefaultSpecifier")) return true;
1864
1880
  if (!isNodeOfType(declaration, "ImportSpecifier")) return false;
@@ -1879,7 +1895,7 @@ const isGeneratedImageRendererCall = (node, scopes) => {
1879
1895
  if (!symbol || symbol.kind !== "import") return false;
1880
1896
  const declaration = symbol.declarationNode;
1881
1897
  if (!isNodeOfType(declaration, "ImportNamespaceSpecifier")) return false;
1882
- const importDeclaration = getImportDeclaration$1(declaration);
1898
+ const importDeclaration = getImportDeclaration(declaration);
1883
1899
  const source = importDeclaration ? getImportSource(importDeclaration) : null;
1884
1900
  return Boolean(source && IMAGE_RESPONSE_MODULES.has(source));
1885
1901
  };
@@ -2099,6 +2115,386 @@ const isGeneratedImageRenderContext = (context, node) => {
2099
2115
  return false;
2100
2116
  };
2101
2117
  //#endregion
2118
+ //#region src/plugin/utils/find-enclosing-function.ts
2119
+ const findEnclosingFunction$1 = (node) => {
2120
+ let cursor = node.parent;
2121
+ while (cursor) {
2122
+ if (isFunctionLike$1(cursor)) return cursor;
2123
+ cursor = cursor.parent ?? null;
2124
+ }
2125
+ return null;
2126
+ };
2127
+ //#endregion
2128
+ //#region src/plugin/utils/find-transparent-expression-root.ts
2129
+ const findTransparentExpressionRoot = (node) => {
2130
+ let current = node;
2131
+ while (current.parent && TRANSPARENT_EXPRESSION_WRAPPER_TYPES.has(current.parent.type)) current = current.parent;
2132
+ return current;
2133
+ };
2134
+ //#endregion
2135
+ //#region src/plugin/constants/js.ts
2136
+ const LOOP_TYPES = [
2137
+ "ForStatement",
2138
+ "ForInStatement",
2139
+ "ForOfStatement",
2140
+ "WhileStatement",
2141
+ "DoWhileStatement"
2142
+ ];
2143
+ const FUNCTION_LIKE_TYPES = new Set([
2144
+ "FunctionDeclaration",
2145
+ "FunctionExpression",
2146
+ "ArrowFunctionExpression"
2147
+ ]);
2148
+ const BUILTIN_GLOBAL_NAMESPACE_NAMES = new Set([
2149
+ "Math",
2150
+ "Date",
2151
+ "JSON",
2152
+ "Object",
2153
+ "Array",
2154
+ "Number",
2155
+ "String",
2156
+ "Boolean",
2157
+ "RegExp",
2158
+ "Symbol",
2159
+ "BigInt",
2160
+ "Reflect"
2161
+ ]);
2162
+ const MUTATING_ARRAY_METHODS = new Set([
2163
+ "push",
2164
+ "pop",
2165
+ "shift",
2166
+ "unshift",
2167
+ "splice",
2168
+ "sort",
2169
+ "reverse",
2170
+ "fill",
2171
+ "copyWithin"
2172
+ ]);
2173
+ const MUTATING_COLLECTION_METHODS = new Set([
2174
+ "add",
2175
+ "clear",
2176
+ "delete",
2177
+ "set"
2178
+ ]);
2179
+ const CHAINABLE_ITERATION_METHODS = new Set([
2180
+ "map",
2181
+ "filter",
2182
+ "forEach",
2183
+ "flatMap"
2184
+ ]);
2185
+ const ITERATOR_PRODUCING_METHOD_NAMES = new Set([
2186
+ "values",
2187
+ "keys",
2188
+ "entries"
2189
+ ]);
2190
+ const BROWSER_TEST_FILE_PATTERN = /\.browser\.[cm]?[jt]sx?$/;
2191
+ const TEST_LIBRARY_IMPORT_SOURCES = new Set([
2192
+ "vitest",
2193
+ "jest",
2194
+ "mocha",
2195
+ "chai",
2196
+ "sinon",
2197
+ "expect",
2198
+ "ava",
2199
+ "uvu",
2200
+ "node:test",
2201
+ "bun:test",
2202
+ "@testing-library/react",
2203
+ "@testing-library/react-native",
2204
+ "@testing-library/react-hooks",
2205
+ "@testing-library/dom",
2206
+ "@testing-library/user-event",
2207
+ "@testing-library/jest-dom",
2208
+ "@testing-library/vue",
2209
+ "@testing-library/svelte",
2210
+ "@testing-library/preact",
2211
+ "@testing-library/cypress",
2212
+ "playwright",
2213
+ "playwright-core",
2214
+ "@playwright/test",
2215
+ "@playwright/experimental-ct-react",
2216
+ "@playwright/experimental-ct-react17",
2217
+ "cypress",
2218
+ "@cypress/react",
2219
+ "@cypress/react18",
2220
+ "@storybook/test",
2221
+ "@storybook/test-runner",
2222
+ "@storybook/testing-library",
2223
+ "@storybook/jest",
2224
+ "puppeteer",
2225
+ "puppeteer-core",
2226
+ "webdriverio",
2227
+ "@wdio/globals",
2228
+ "@nuxt/test-utils"
2229
+ ]);
2230
+ const TEST_LIBRARY_IMPORT_SOURCE_PREFIXES = [
2231
+ "vitest/",
2232
+ "@vitest/",
2233
+ "@jest/",
2234
+ "@testing-library/",
2235
+ "@playwright/",
2236
+ "@storybook/test/",
2237
+ "@storybook/test-runner/",
2238
+ "@storybook/testing-library/",
2239
+ "@cypress/",
2240
+ "@nuxt/test-utils/"
2241
+ ];
2242
+ const ORDERED_UI_FLOW_CALLEE_NAMES = new Set([
2243
+ "render",
2244
+ "rerender",
2245
+ "renderHook",
2246
+ "renderToString",
2247
+ "renderToStaticMarkup",
2248
+ "act",
2249
+ "click",
2250
+ "dblClick",
2251
+ "dblclick",
2252
+ "tripleClick",
2253
+ "tap",
2254
+ "press",
2255
+ "longPress",
2256
+ "type",
2257
+ "clear",
2258
+ "fill",
2259
+ "focus",
2260
+ "blur",
2261
+ "hover",
2262
+ "unhover",
2263
+ "check",
2264
+ "uncheck",
2265
+ "selectOption",
2266
+ "selectOptions",
2267
+ "setChecked",
2268
+ "setInputFiles",
2269
+ "scrollIntoViewIfNeeded",
2270
+ "dragTo",
2271
+ "dragAndDrop",
2272
+ "drop",
2273
+ "evaluate",
2274
+ "evaluateHandle",
2275
+ "waitFor",
2276
+ "waitForLoadState",
2277
+ "waitForSelector",
2278
+ "waitForURL",
2279
+ "waitForResponse",
2280
+ "waitForRequest",
2281
+ "waitForEvent",
2282
+ "waitForFunction",
2283
+ "waitForElementToBeRemoved",
2284
+ "goto",
2285
+ "goBack",
2286
+ "goForward",
2287
+ "reload",
2288
+ "screenshot",
2289
+ "snapshot",
2290
+ "toMatchSnapshot",
2291
+ "toMatchInlineSnapshot",
2292
+ "expect",
2293
+ "expectTypeOf",
2294
+ "step",
2295
+ "describe",
2296
+ "test",
2297
+ "it",
2298
+ "beforeAll",
2299
+ "beforeEach",
2300
+ "afterAll",
2301
+ "afterEach",
2302
+ "play",
2303
+ "userEvent",
2304
+ "screen",
2305
+ "within"
2306
+ ]);
2307
+ const ORDERED_UI_FLOW_CALLEE_PREFIXES = ["findBy", "findAllBy"];
2308
+ const INTENTIONAL_SEQUENCING_CALLEE_NAMES = new Set([
2309
+ "sleep",
2310
+ "delay",
2311
+ "wait",
2312
+ "pause",
2313
+ "throttle",
2314
+ "debounce",
2315
+ "tick",
2316
+ "nextTick",
2317
+ "advanceTimersByTime",
2318
+ "advanceTimersByTimeAsync",
2319
+ "runAllTimers",
2320
+ "runAllTimersAsync",
2321
+ "runOnlyPendingTimers",
2322
+ "runOnlyPendingTimersAsync",
2323
+ "setTimeout",
2324
+ "setInterval",
2325
+ "setImmediate",
2326
+ "queueMicrotask",
2327
+ "requestAnimationFrame",
2328
+ "requestIdleCallback",
2329
+ "animate",
2330
+ "transition",
2331
+ "spring",
2332
+ "tween",
2333
+ "stagger",
2334
+ "sequence",
2335
+ "timeline",
2336
+ "scrub",
2337
+ "query",
2338
+ "execute",
2339
+ "exec",
2340
+ "raw",
2341
+ "transaction",
2342
+ "$transaction",
2343
+ "$executeRaw",
2344
+ "$queryRaw",
2345
+ "$executeRawUnsafe",
2346
+ "$queryRawUnsafe",
2347
+ "begin",
2348
+ "commit",
2349
+ "rollback",
2350
+ "savepoint",
2351
+ "lock",
2352
+ "unlock",
2353
+ "spawn",
2354
+ "spawnSync",
2355
+ "execSync",
2356
+ "execFile",
2357
+ "execFileSync",
2358
+ "fork",
2359
+ "$",
2360
+ "sh",
2361
+ "mkdir",
2362
+ "rmdir",
2363
+ "rename",
2364
+ "rm",
2365
+ "unlink",
2366
+ "writeFile",
2367
+ "appendFile",
2368
+ "copyFile",
2369
+ "navigate",
2370
+ "goto",
2371
+ "waitForNavigation",
2372
+ "waitForURL",
2373
+ "waitForLoadState",
2374
+ "waitForResponse",
2375
+ "waitForRequest",
2376
+ "waitForSelector",
2377
+ "waitForFunction",
2378
+ "waitForEvent"
2379
+ ]);
2380
+ //#endregion
2381
+ //#region src/plugin/utils/is-test-library-import-source.ts
2382
+ const isTestLibraryImportSource = (source) => {
2383
+ if (typeof source !== "string" || source.length === 0) return false;
2384
+ if (TEST_LIBRARY_IMPORT_SOURCES.has(source)) return true;
2385
+ return TEST_LIBRARY_IMPORT_SOURCE_PREFIXES.some((prefix) => source.startsWith(prefix));
2386
+ };
2387
+ //#endregion
2388
+ //#region src/plugin/utils/is-local-test-scaffold-jsx.ts
2389
+ const TEST_CALLBACK_EXPORT_NAMES = new Set(["it", "test"]);
2390
+ const TEST_CALLBACK_MEMBER_NAMES = new Set([
2391
+ "concurrent",
2392
+ "only",
2393
+ "skip"
2394
+ ]);
2395
+ const TEST_CALLBACK_TABLE_MEMBER_NAME = "each";
2396
+ const TEST_MOCK_METHOD_NAMES = new Set([
2397
+ "doMock",
2398
+ "mock",
2399
+ "unstable_mockModule"
2400
+ ]);
2401
+ const TEST_RUNTIME_EXPORT_NAMES = new Set(["jest", "vi"]);
2402
+ const TEST_RUNTIME_MODULE_SOURCES = new Set([
2403
+ "@jest/globals",
2404
+ "bun:test",
2405
+ "node:test",
2406
+ "vitest"
2407
+ ]);
2408
+ const REACT_MODULE_SOURCES = new Set([
2409
+ "react",
2410
+ "react/jsx-dev-runtime",
2411
+ "react/jsx-runtime"
2412
+ ]);
2413
+ const hasUnitTestFilename = (rawFilename) => {
2414
+ if (!rawFilename) return false;
2415
+ const filename = `/${rawFilename.replaceAll("\\", "/")}`;
2416
+ const basename = filename.slice(filename.lastIndexOf("/") + 1);
2417
+ return basename.includes(".test.") || basename.includes(".spec.") || filename.includes("/__tests__/") || filename.includes("/__test__/") || filename.includes("/__mocks__/");
2418
+ };
2419
+ const isExactImportedBinding = (identifier, expectedExportNames, context) => {
2420
+ if (context.scopes.referenceFor(identifier)?.resolvedSymbol?.kind !== "import") return false;
2421
+ const importBinding = getImportBindingForName(identifier, identifier.name);
2422
+ return Boolean(importBinding && TEST_RUNTIME_MODULE_SOURCES.has(importBinding.source) && importBinding.exportedName && expectedExportNames.has(importBinding.exportedName));
2423
+ };
2424
+ const isRecognizedTestGlobal = (identifier, expectedNames, context) => hasUnitTestFilename(context.filename) && expectedNames.has(identifier.name) && context.scopes.isGlobalReference(identifier);
2425
+ const isRecognizedTestBinding = (identifier, expectedNames, context) => isExactImportedBinding(identifier, expectedNames, context) || isRecognizedTestGlobal(identifier, expectedNames, context);
2426
+ const getTestCallbackBaseIdentifier = (callee) => {
2427
+ const unwrappedCallee = stripParenExpression(callee);
2428
+ if (isNodeOfType(unwrappedCallee, "Identifier")) return unwrappedCallee;
2429
+ if (isNodeOfType(unwrappedCallee, "MemberExpression")) {
2430
+ const memberName = getStaticPropertyName(unwrappedCallee);
2431
+ if (!memberName || !TEST_CALLBACK_MEMBER_NAMES.has(memberName)) return null;
2432
+ return getTestCallbackBaseIdentifier(unwrappedCallee.object);
2433
+ }
2434
+ const tableBuilderCallee = isNodeOfType(unwrappedCallee, "CallExpression") ? stripParenExpression(unwrappedCallee.callee) : isNodeOfType(unwrappedCallee, "TaggedTemplateExpression") ? stripParenExpression(unwrappedCallee.tag) : null;
2435
+ if (!isNodeOfType(tableBuilderCallee, "MemberExpression")) return null;
2436
+ if (getStaticPropertyName(tableBuilderCallee) !== TEST_CALLBACK_TABLE_MEMBER_NAME) return null;
2437
+ return getTestCallbackBaseIdentifier(tableBuilderCallee.object);
2438
+ };
2439
+ const isDirectTestCallback = (functionNode, context) => {
2440
+ const callbackRoot = findTransparentExpressionRoot(functionNode);
2441
+ const callExpression = callbackRoot.parent;
2442
+ if (!callExpression || !isNodeOfType(callExpression, "CallExpression")) return false;
2443
+ if (!callExpression.arguments.some((argument) => argument === callbackRoot)) return false;
2444
+ const baseIdentifier = getTestCallbackBaseIdentifier(callExpression.callee);
2445
+ return Boolean(baseIdentifier && isRecognizedTestBinding(baseIdentifier, TEST_CALLBACK_EXPORT_NAMES, context));
2446
+ };
2447
+ const isRecognizedMockFactoryCall = (callExpression, factoryRoot, context) => {
2448
+ if (callExpression.arguments[1] !== factoryRoot) return false;
2449
+ const moduleSpecifier = callExpression.arguments[0];
2450
+ if (!moduleSpecifier || !isNodeOfType(moduleSpecifier, "Literal")) return false;
2451
+ if (typeof moduleSpecifier.value !== "string") return false;
2452
+ const callee = stripParenExpression(callExpression.callee);
2453
+ if (!isNodeOfType(callee, "MemberExpression")) return false;
2454
+ const methodName = getStaticPropertyName(callee);
2455
+ if (!methodName || !TEST_MOCK_METHOD_NAMES.has(methodName)) return false;
2456
+ const receiver = stripParenExpression(callee.object);
2457
+ return isNodeOfType(receiver, "Identifier") && isRecognizedTestBinding(receiver, TEST_RUNTIME_EXPORT_NAMES, context);
2458
+ };
2459
+ const isInsideRecognizedMockFactory = (node, context) => {
2460
+ let current = node.parent;
2461
+ while (current) {
2462
+ if (isFunctionLike$1(current)) {
2463
+ const factoryRoot = findTransparentExpressionRoot(current);
2464
+ const callExpression = factoryRoot.parent;
2465
+ if (callExpression && isNodeOfType(callExpression, "CallExpression") && isRecognizedMockFactoryCall(callExpression, factoryRoot, context)) return true;
2466
+ }
2467
+ current = current.parent;
2468
+ }
2469
+ return false;
2470
+ };
2471
+ const hasImportedProductComponentAttributeAncestor = (node, enclosingFunction, context) => {
2472
+ let current = node.parent;
2473
+ let attributeAncestor = null;
2474
+ if (current && isNodeOfType(current, "JSXElement") && current.openingElement === node) current = current.parent;
2475
+ while (current && current !== enclosingFunction) {
2476
+ if (isFunctionLike$1(current)) return false;
2477
+ if (isNodeOfType(current, "JSXAttribute")) attributeAncestor = current;
2478
+ if (isNodeOfType(current, "JSXElement")) {
2479
+ const componentName = current.openingElement.name;
2480
+ if (isNodeOfType(componentName, "JSXIdentifier")) {
2481
+ const reference = context.scopes.referenceFor(componentName);
2482
+ const importBinding = getImportBindingForName(componentName, componentName.name);
2483
+ if (reference?.resolvedSymbol?.kind === "import" && importBinding && !REACT_MODULE_SOURCES.has(importBinding.source) && !isTestLibraryImportSource(importBinding.source) && attributeAncestor?.parent === current.openingElement && isNodeOfType(attributeAncestor.name, "JSXIdentifier") && attributeAncestor.name.name !== "children") return true;
2484
+ }
2485
+ attributeAncestor = null;
2486
+ }
2487
+ current = current.parent;
2488
+ }
2489
+ return false;
2490
+ };
2491
+ const isLocalTestScaffoldJsx = (node, context) => {
2492
+ if (isInsideRecognizedMockFactory(node, context)) return true;
2493
+ const enclosingFunction = findEnclosingFunction$1(node);
2494
+ if (!enclosingFunction || !isDirectTestCallback(enclosingFunction, context)) return false;
2495
+ return hasImportedProductComponentAttributeAncestor(node, enclosingFunction, context);
2496
+ };
2497
+ //#endregion
2102
2498
  //#region src/plugin/utils/object-has-accessible-child.ts
2103
2499
  const objectHasAccessibleChild = (jsxElement, settings) => {
2104
2500
  for (const child of jsxElement.children) {
@@ -2267,6 +2663,7 @@ const altText = defineRule({
2267
2663
  const inputImageAliases = new Set(settings["input[type=\"image\"]"] ?? []);
2268
2664
  const fileHasJsxA11ySettings = hasJsxA11ySettings(context.settings);
2269
2665
  return { JSXOpeningElement(node) {
2666
+ if (isLocalTestScaffoldJsx(node, context)) return;
2270
2667
  if (!fileHasJsxA11ySettings && isNodeOfType(node.name, "JSXIdentifier")) {
2271
2668
  const rawName = node.name.name;
2272
2669
  if (rawName !== "img" && rawName !== "object" && rawName !== "area" && rawName.toLowerCase() !== "input" && !imgAliases.has(rawName) && !objectAliases.has(rawName) && !areaAliases.has(rawName) && !inputImageAliases.has(rawName)) return;
@@ -4135,252 +4532,6 @@ const artifactSecretLeak = defineRule({
4135
4532
  scan: (file) => scanArtifactLeak(file, (content) => SECRET_VALUE_PATTERNS.find((pattern) => pattern.test(content)), "A browser-delivered artifact contains a secret-looking credential value.")
4136
4533
  });
4137
4534
  //#endregion
4138
- //#region src/plugin/constants/js.ts
4139
- const LOOP_TYPES = [
4140
- "ForStatement",
4141
- "ForInStatement",
4142
- "ForOfStatement",
4143
- "WhileStatement",
4144
- "DoWhileStatement"
4145
- ];
4146
- const FUNCTION_LIKE_TYPES = new Set([
4147
- "FunctionDeclaration",
4148
- "FunctionExpression",
4149
- "ArrowFunctionExpression"
4150
- ]);
4151
- const BUILTIN_GLOBAL_NAMESPACE_NAMES = new Set([
4152
- "Math",
4153
- "Date",
4154
- "JSON",
4155
- "Object",
4156
- "Array",
4157
- "Number",
4158
- "String",
4159
- "Boolean",
4160
- "RegExp",
4161
- "Symbol",
4162
- "BigInt",
4163
- "Reflect"
4164
- ]);
4165
- const MUTATING_ARRAY_METHODS = new Set([
4166
- "push",
4167
- "pop",
4168
- "shift",
4169
- "unshift",
4170
- "splice",
4171
- "sort",
4172
- "reverse",
4173
- "fill",
4174
- "copyWithin"
4175
- ]);
4176
- const MUTATING_COLLECTION_METHODS = new Set([
4177
- "add",
4178
- "clear",
4179
- "delete",
4180
- "set"
4181
- ]);
4182
- const CHAINABLE_ITERATION_METHODS = new Set([
4183
- "map",
4184
- "filter",
4185
- "forEach",
4186
- "flatMap"
4187
- ]);
4188
- const ITERATOR_PRODUCING_METHOD_NAMES = new Set([
4189
- "values",
4190
- "keys",
4191
- "entries"
4192
- ]);
4193
- const BROWSER_TEST_FILE_PATTERN = /\.browser\.[cm]?[jt]sx?$/;
4194
- const TEST_LIBRARY_IMPORT_SOURCES = new Set([
4195
- "vitest",
4196
- "jest",
4197
- "mocha",
4198
- "chai",
4199
- "sinon",
4200
- "expect",
4201
- "ava",
4202
- "uvu",
4203
- "node:test",
4204
- "bun:test",
4205
- "@testing-library/react",
4206
- "@testing-library/react-native",
4207
- "@testing-library/react-hooks",
4208
- "@testing-library/dom",
4209
- "@testing-library/user-event",
4210
- "@testing-library/jest-dom",
4211
- "@testing-library/vue",
4212
- "@testing-library/svelte",
4213
- "@testing-library/preact",
4214
- "@testing-library/cypress",
4215
- "playwright",
4216
- "playwright-core",
4217
- "@playwright/test",
4218
- "@playwright/experimental-ct-react",
4219
- "@playwright/experimental-ct-react17",
4220
- "cypress",
4221
- "@cypress/react",
4222
- "@cypress/react18",
4223
- "@storybook/test",
4224
- "@storybook/test-runner",
4225
- "@storybook/testing-library",
4226
- "@storybook/jest",
4227
- "puppeteer",
4228
- "puppeteer-core",
4229
- "webdriverio",
4230
- "@wdio/globals",
4231
- "@nuxt/test-utils"
4232
- ]);
4233
- const TEST_LIBRARY_IMPORT_SOURCE_PREFIXES = [
4234
- "vitest/",
4235
- "@vitest/",
4236
- "@jest/",
4237
- "@testing-library/",
4238
- "@playwright/",
4239
- "@storybook/test/",
4240
- "@storybook/test-runner/",
4241
- "@storybook/testing-library/",
4242
- "@cypress/",
4243
- "@nuxt/test-utils/"
4244
- ];
4245
- const ORDERED_UI_FLOW_CALLEE_NAMES = new Set([
4246
- "render",
4247
- "rerender",
4248
- "renderHook",
4249
- "renderToString",
4250
- "renderToStaticMarkup",
4251
- "act",
4252
- "click",
4253
- "dblClick",
4254
- "dblclick",
4255
- "tripleClick",
4256
- "tap",
4257
- "press",
4258
- "longPress",
4259
- "type",
4260
- "clear",
4261
- "fill",
4262
- "focus",
4263
- "blur",
4264
- "hover",
4265
- "unhover",
4266
- "check",
4267
- "uncheck",
4268
- "selectOption",
4269
- "selectOptions",
4270
- "setChecked",
4271
- "setInputFiles",
4272
- "scrollIntoViewIfNeeded",
4273
- "dragTo",
4274
- "dragAndDrop",
4275
- "drop",
4276
- "evaluate",
4277
- "evaluateHandle",
4278
- "waitFor",
4279
- "waitForLoadState",
4280
- "waitForSelector",
4281
- "waitForURL",
4282
- "waitForResponse",
4283
- "waitForRequest",
4284
- "waitForEvent",
4285
- "waitForFunction",
4286
- "waitForElementToBeRemoved",
4287
- "goto",
4288
- "goBack",
4289
- "goForward",
4290
- "reload",
4291
- "screenshot",
4292
- "snapshot",
4293
- "toMatchSnapshot",
4294
- "toMatchInlineSnapshot",
4295
- "expect",
4296
- "expectTypeOf",
4297
- "step",
4298
- "describe",
4299
- "test",
4300
- "it",
4301
- "beforeAll",
4302
- "beforeEach",
4303
- "afterAll",
4304
- "afterEach",
4305
- "play",
4306
- "userEvent",
4307
- "screen",
4308
- "within"
4309
- ]);
4310
- const ORDERED_UI_FLOW_CALLEE_PREFIXES = ["findBy", "findAllBy"];
4311
- const INTENTIONAL_SEQUENCING_CALLEE_NAMES = new Set([
4312
- "sleep",
4313
- "delay",
4314
- "wait",
4315
- "pause",
4316
- "throttle",
4317
- "debounce",
4318
- "tick",
4319
- "nextTick",
4320
- "advanceTimersByTime",
4321
- "advanceTimersByTimeAsync",
4322
- "runAllTimers",
4323
- "runAllTimersAsync",
4324
- "runOnlyPendingTimers",
4325
- "runOnlyPendingTimersAsync",
4326
- "setTimeout",
4327
- "setInterval",
4328
- "setImmediate",
4329
- "queueMicrotask",
4330
- "requestAnimationFrame",
4331
- "requestIdleCallback",
4332
- "animate",
4333
- "transition",
4334
- "spring",
4335
- "tween",
4336
- "stagger",
4337
- "sequence",
4338
- "timeline",
4339
- "scrub",
4340
- "query",
4341
- "execute",
4342
- "exec",
4343
- "raw",
4344
- "transaction",
4345
- "$transaction",
4346
- "$executeRaw",
4347
- "$queryRaw",
4348
- "$executeRawUnsafe",
4349
- "$queryRawUnsafe",
4350
- "begin",
4351
- "commit",
4352
- "rollback",
4353
- "savepoint",
4354
- "lock",
4355
- "unlock",
4356
- "spawn",
4357
- "spawnSync",
4358
- "execSync",
4359
- "execFile",
4360
- "execFileSync",
4361
- "fork",
4362
- "$",
4363
- "sh",
4364
- "mkdir",
4365
- "rmdir",
4366
- "rename",
4367
- "rm",
4368
- "unlink",
4369
- "writeFile",
4370
- "appendFile",
4371
- "copyFile",
4372
- "navigate",
4373
- "goto",
4374
- "waitForNavigation",
4375
- "waitForURL",
4376
- "waitForLoadState",
4377
- "waitForResponse",
4378
- "waitForRequest",
4379
- "waitForSelector",
4380
- "waitForFunction",
4381
- "waitForEvent"
4382
- ]);
4383
- //#endregion
4384
4535
  //#region src/plugin/constants/ts-type-position-keys.ts
4385
4536
  const TYPE_POSITION_CHILD_KEYS = new Set([
4386
4537
  "implements",
@@ -4506,13 +4657,6 @@ const containsDirectAwait = (node) => {
4506
4657
  return foundAwait;
4507
4658
  };
4508
4659
  //#endregion
4509
- //#region src/plugin/utils/find-transparent-expression-root.ts
4510
- const findTransparentExpressionRoot = (node) => {
4511
- let current = node;
4512
- while (current.parent && TRANSPARENT_EXPRESSION_WRAPPER_TYPES.has(current.parent.type)) current = current.parent;
4513
- return current;
4514
- };
4515
- //#endregion
4516
4660
  //#region src/plugin/utils/get-static-property-key-name.ts
4517
4661
  const getStaticPropertyKeyName = (node, options = {}) => {
4518
4662
  if (!isNodeOfType(node, "Property") && !isNodeOfType(node, "MethodDefinition") && !isNodeOfType(node, "MemberExpression")) return null;
@@ -4772,16 +4916,6 @@ const hasStaticPropertyWriteBefore = (identifier, propertyName, referenceNode, s
4772
4916
  return getEquivalentSymbols(identifier, scopes).some((symbol) => symbolHasStaticPropertyWriteBefore(symbol, propertyName, referenceNode, scopes));
4773
4917
  };
4774
4918
  //#endregion
4775
- //#region src/plugin/utils/find-enclosing-function.ts
4776
- const findEnclosingFunction$1 = (node) => {
4777
- let cursor = node.parent;
4778
- while (cursor) {
4779
- if (isFunctionLike$1(cursor)) return cursor;
4780
- cursor = cursor.parent ?? null;
4781
- }
4782
- return null;
4783
- };
4784
- //#endregion
4785
4919
  //#region src/plugin/utils/has-symbol-write-before.ts
4786
4920
  const hasSymbolWriteBefore = (symbol, referenceNode, scopes) => symbol.references.some((reference) => {
4787
4921
  if (reference.flag === "read") return false;
@@ -6283,13 +6417,6 @@ const getCalleeIdentifierTrail = (call) => {
6283
6417
  return trail;
6284
6418
  };
6285
6419
  //#endregion
6286
- //#region src/plugin/utils/is-test-library-import-source.ts
6287
- const isTestLibraryImportSource = (source) => {
6288
- if (typeof source !== "string" || source.length === 0) return false;
6289
- if (TEST_LIBRARY_IMPORT_SOURCES.has(source)) return true;
6290
- return TEST_LIBRARY_IMPORT_SOURCE_PREFIXES.some((prefix) => source.startsWith(prefix));
6291
- };
6292
- //#endregion
6293
6420
  //#region src/plugin/rules/js-performance/async-parallel.ts
6294
6421
  const getAwaitedCall = (statement) => {
6295
6422
  if (isNodeOfType(statement, "VariableDeclaration")) {
@@ -8251,7 +8378,27 @@ const collectFunctionReturnStatements = (functionNode) => {
8251
8378
  //#region src/plugin/utils/statement-always-exits.ts
8252
8379
  const statementAlwaysExits = (statement) => {
8253
8380
  if (isNodeOfType(statement, "ReturnStatement") || isNodeOfType(statement, "ThrowStatement")) return true;
8254
- if (isNodeOfType(statement, "IfStatement")) return Boolean(statement.alternate && statementAlwaysExits(statement.consequent) && statementAlwaysExits(statement.alternate));
8381
+ if (isNodeOfType(statement, "IfStatement")) {
8382
+ if (isNodeOfType(statement.test, "Literal")) {
8383
+ const reachableBranch = statement.test.value ? statement.consequent : statement.alternate;
8384
+ return reachableBranch ? statementAlwaysExits(reachableBranch) : false;
8385
+ }
8386
+ return Boolean(statement.alternate && statementAlwaysExits(statement.consequent) && statementAlwaysExits(statement.alternate));
8387
+ }
8388
+ if (isNodeOfType(statement, "TryStatement")) {
8389
+ if (statement.finalizer && statementAlwaysExits(statement.finalizer)) return true;
8390
+ if (!statementAlwaysExits(statement.block)) return false;
8391
+ return statement.handler ? statementAlwaysExits(statement.handler.body) : true;
8392
+ }
8393
+ if (isNodeOfType(statement, "DoWhileStatement")) return statementAlwaysExits(statement.body);
8394
+ if (isNodeOfType(statement, "WhileStatement")) {
8395
+ const whileStatementTest = statement.test;
8396
+ return Boolean(isNodeOfType(whileStatementTest, "Literal") && whileStatementTest.value && statementAlwaysExits(statement.body));
8397
+ }
8398
+ if (isNodeOfType(statement, "ForStatement")) {
8399
+ const forStatementTest = statement.test;
8400
+ return Boolean((!forStatementTest || isNodeOfType(forStatementTest, "Literal") && forStatementTest.value) && statementAlwaysExits(statement.body));
8401
+ }
8255
8402
  if (!isNodeOfType(statement, "BlockStatement")) return false;
8256
8403
  return statement.body.some((childStatement) => statementAlwaysExits(childStatement));
8257
8404
  };
@@ -10370,10 +10517,10 @@ const controlHasAssociatedLabel = defineRule({
10370
10517
  if (isTestlikeFile) return;
10371
10518
  const opening = node.openingElement;
10372
10519
  const tagName = getElementType(opening, context.settings);
10373
- if (tagName === LABEL_ELEMENT && hasAccessibleLabelText(node, checkContext) && !isInsideJsxAttribute(node)) {
10520
+ if (rendersLabelElement(tagName, opening) && hasAccessibleLabelText(node, checkContext) && !isInsideJsxAttribute(node)) {
10374
10521
  const htmlForAttribute = hasJsxPropIgnoreCase(opening.attributes, HTML_FOR_ATTRIBUTE);
10375
10522
  for (const htmlForKey of getAttributeMatchKeys(htmlForAttribute)) labelHtmlForKeys.add(htmlForKey);
10376
- collectLabelEmbeddedNames(node, 1, checkContext, labelEmbeddedNames);
10523
+ if (tagName === LABEL_ELEMENT) collectLabelEmbeddedNames(node, 1, checkContext, labelEmbeddedNames);
10377
10524
  }
10378
10525
  if (DEFAULT_IGNORE_ELEMENTS.includes(tagName)) return;
10379
10526
  if (settings.ignoreElements.includes(tagName)) return;
@@ -10459,6 +10606,1048 @@ const findMatchingBracket = (content, openIndex) => {
10459
10606
  return -1;
10460
10607
  };
10461
10608
  //#endregion
10609
+ //#region src/plugin/utils/get-node-end-index.ts
10610
+ const getNodeEndIndex = (node) => "end" in node && typeof node.end === "number" ? node.end : -1;
10611
+ //#endregion
10612
+ //#region src/plugin/utils/get-node-start-index.ts
10613
+ const getNodeStartIndex = (node) => "start" in node && typeof node.start === "number" ? node.start : -1;
10614
+ //#endregion
10615
+ //#region src/plugin/utils/get-import-declaration-for-symbol.ts
10616
+ const getImportDeclarationForSymbol = (symbol) => {
10617
+ if (symbol.kind !== "import") return null;
10618
+ const importDeclaration = symbol.declarationNode.parent;
10619
+ return isNodeOfType(importDeclaration, "ImportDeclaration") ? importDeclaration : null;
10620
+ };
10621
+ //#endregion
10622
+ //#region src/plugin/constants/mutation-methods.ts
10623
+ const OBJECT_PROPERTY_MUTATION_METHOD_NAMES = new Set([
10624
+ "assign",
10625
+ "defineProperties",
10626
+ "defineProperty"
10627
+ ]);
10628
+ const REFLECT_PROPERTY_MUTATION_METHOD_NAMES = new Set(["defineProperty", "set"]);
10629
+ //#endregion
10630
+ //#region src/plugin/rules/security-scan/utils/get-symbol-mutation-inspector.ts
10631
+ const inspectorCache = /* @__PURE__ */ new WeakMap();
10632
+ const getOutermostTarget = (node) => {
10633
+ let current = findTransparentExpressionRoot(node);
10634
+ while (current.parent) {
10635
+ const parent = current.parent;
10636
+ if (!isNodeOfType(parent, "MemberExpression") || parent.object !== current) break;
10637
+ current = findTransparentExpressionRoot(parent);
10638
+ }
10639
+ return current;
10640
+ };
10641
+ const getExecutionOwner = (node) => {
10642
+ let current = node;
10643
+ while (current) {
10644
+ if (isFunctionLike$1(current) || isNodeOfType(current, "Program")) return current;
10645
+ current = current.parent;
10646
+ }
10647
+ return node;
10648
+ };
10649
+ const isAbruptCompletionStatement = (node, includesContinue) => {
10650
+ if (isNodeOfType(node, "ReturnStatement") || isNodeOfType(node, "ThrowStatement") || isNodeOfType(node, "BreakStatement") || includesContinue && isNodeOfType(node, "ContinueStatement")) return true;
10651
+ if (isNodeOfType(node, "BlockStatement")) return node.body.some((statement) => isAbruptCompletionStatement(statement, includesContinue));
10652
+ if (!isNodeOfType(node, "IfStatement")) return false;
10653
+ if (isNodeOfType(node.test, "Literal")) {
10654
+ const reachableBranch = node.test.value ? node.consequent : node.alternate;
10655
+ return reachableBranch ? isAbruptCompletionStatement(reachableBranch, includesContinue) : false;
10656
+ }
10657
+ return Boolean(node.alternate && isAbruptCompletionStatement(node.consequent, includesContinue) && isAbruptCompletionStatement(node.alternate, includesContinue));
10658
+ };
10659
+ const isTerminalStatement = (node) => isAbruptCompletionStatement(node, true);
10660
+ const isAfterTerminalStatement = (node, statements) => {
10661
+ const statementIndex = statements.indexOf(node);
10662
+ return statementIndex > 0 && statements.slice(0, statementIndex).some(isTerminalStatement);
10663
+ };
10664
+ const isStaticallyUnreachable = (node, owner) => {
10665
+ let current = node;
10666
+ while (current.parent && current !== owner) {
10667
+ const parent = current.parent;
10668
+ if ((isNodeOfType(parent, "BlockStatement") || isNodeOfType(parent, "Program")) && isAfterTerminalStatement(current, parent.body)) return true;
10669
+ if ((isNodeOfType(parent, "WhileStatement") && isNodeOfType(parent.test, "Literal") && !parent.test.value || isNodeOfType(parent, "ForStatement") && parent.test && isNodeOfType(parent.test, "Literal") && !parent.test.value) && parent.body === current) return true;
10670
+ if (isNodeOfType(parent, "SwitchCase") && isAfterTerminalStatement(current, parent.consequent)) return true;
10671
+ if (isNodeOfType(parent, "IfStatement") && isNodeOfType(parent.test, "Literal")) {
10672
+ if (parent.test.value === false && parent.consequent === current) return true;
10673
+ if (parent.test.value === true && parent.alternate === current) return true;
10674
+ }
10675
+ if (isNodeOfType(parent, "ConditionalExpression") && isNodeOfType(parent.test, "Literal")) {
10676
+ if (parent.test.value === false && parent.consequent === current) return true;
10677
+ if (parent.test.value === true && parent.alternate === current) return true;
10678
+ }
10679
+ if (isNodeOfType(parent, "LogicalExpression") && parent.right === current && isNodeOfType(parent.left, "Literal")) {
10680
+ if (parent.operator === "&&" && !parent.left.value) return true;
10681
+ if (parent.operator === "||" && Boolean(parent.left.value)) return true;
10682
+ }
10683
+ current = parent;
10684
+ }
10685
+ return false;
10686
+ };
10687
+ const isConditionallyExecuted = (node, owner) => {
10688
+ let current = node;
10689
+ while (current.parent && current !== owner) {
10690
+ const parent = current.parent;
10691
+ if (isNodeOfType(parent, "IfStatement")) {
10692
+ if (!isNodeOfType(parent.test, "Literal")) return true;
10693
+ if (parent.test.value === true && parent.alternate === current) return true;
10694
+ if (parent.test.value === false && parent.consequent === current) return true;
10695
+ }
10696
+ if (isNodeOfType(parent, "ConditionalExpression")) {
10697
+ if (!isNodeOfType(parent.test, "Literal")) return true;
10698
+ if (parent.test.value === true && parent.alternate === current) return true;
10699
+ if (parent.test.value === false && parent.consequent === current) return true;
10700
+ }
10701
+ if (isNodeOfType(parent, "LogicalExpression") && parent.right === current) {
10702
+ if (!isNodeOfType(parent.left, "Literal")) return true;
10703
+ if (parent.operator === "&&" && !parent.left.value) return true;
10704
+ if (parent.operator === "||" && Boolean(parent.left.value)) return true;
10705
+ }
10706
+ if (isNodeOfType(parent, "DoWhileStatement")) {
10707
+ if (!(parent.body === current && isNodeOfType(parent.test, "Literal") && !parent.test.value)) return true;
10708
+ }
10709
+ if (isNodeOfType(parent, "TryStatement") && parent.block === current) return true;
10710
+ if (isNodeOfType(parent, "ForStatement") || isNodeOfType(parent, "ForInStatement") || isNodeOfType(parent, "ForOfStatement") || isNodeOfType(parent, "WhileStatement") || isNodeOfType(parent, "SwitchCase") || isNodeOfType(parent, "CatchClause")) return true;
10711
+ if ((isNodeOfType(parent, "CallExpression") || isNodeOfType(parent, "MemberExpression")) && parent.optional) return true;
10712
+ current = parent;
10713
+ }
10714
+ return false;
10715
+ };
10716
+ const getSymbolMutationInspector = (scopes) => {
10717
+ const cached = inspectorCache.get(scopes);
10718
+ if (cached) return cached;
10719
+ const isGlobalNamespaceMethod = (node, namespaceName, methodNames) => {
10720
+ const callee = stripParenExpression(node);
10721
+ if (!isNodeOfType(callee, "MemberExpression")) return false;
10722
+ const receiver = stripParenExpression(callee.object);
10723
+ return Boolean(isNodeOfType(receiver, "Identifier") && receiver.name === namespaceName && scopes.isGlobalReference(receiver) && methodNames.has(getStaticPropertyName(callee) ?? ""));
10724
+ };
10725
+ const getObjectExpressionPropertyNames = (node) => {
10726
+ const expression = stripParenExpression(node);
10727
+ if (!isNodeOfType(expression, "ObjectExpression")) return null;
10728
+ const propertyNames = /* @__PURE__ */ new Set();
10729
+ for (const property of expression.properties) {
10730
+ if (!isNodeOfType(property, "Property")) return null;
10731
+ const propertyName = getStaticPropertyKeyName(property, { allowComputedString: true });
10732
+ if (propertyName === null) return null;
10733
+ propertyNames.add(propertyName);
10734
+ }
10735
+ return propertyNames;
10736
+ };
10737
+ const getMutationPropertyNames = (node) => {
10738
+ const target = getOutermostTarget(node);
10739
+ const parent = target.parent;
10740
+ if (!parent) return void 0;
10741
+ if (isNodeOfType(parent, "AssignmentExpression") && parent.left === target || isNodeOfType(parent, "UpdateExpression") && parent.argument === target || isNodeOfType(parent, "UnaryExpression") && parent.operator === "delete") {
10742
+ if (!isNodeOfType(target, "MemberExpression")) return null;
10743
+ const propertyName = getStaticPropertyName(target);
10744
+ return propertyName === null ? null : new Set([propertyName]);
10745
+ }
10746
+ if (!isNodeOfType(parent, "CallExpression") || parent.arguments[0] !== target) return void 0;
10747
+ if (isGlobalNamespaceMethod(parent.callee, "Object", OBJECT_PROPERTY_MUTATION_METHOD_NAMES)) {
10748
+ const callee = stripParenExpression(parent.callee);
10749
+ if (!isNodeOfType(callee, "MemberExpression")) return void 0;
10750
+ const methodName = getStaticPropertyName(callee);
10751
+ if (methodName === "assign") {
10752
+ const assignedProperties = parent.arguments.slice(1).map(getObjectExpressionPropertyNames);
10753
+ if (assignedProperties.some((properties) => properties === null)) return null;
10754
+ return new Set(assignedProperties.flatMap((properties) => [...properties ?? []]));
10755
+ }
10756
+ if (methodName === "defineProperties") {
10757
+ const propertyDescriptors = parent.arguments[1];
10758
+ return propertyDescriptors ? getObjectExpressionPropertyNames(propertyDescriptors) : null;
10759
+ }
10760
+ const propertyKey = parent.arguments[1];
10761
+ return propertyKey && isNodeOfType(propertyKey, "Literal") && typeof propertyKey.value === "string" ? new Set([propertyKey.value]) : null;
10762
+ }
10763
+ if (isGlobalNamespaceMethod(parent.callee, "Reflect", REFLECT_PROPERTY_MUTATION_METHOD_NAMES)) {
10764
+ const propertyKey = parent.arguments[1];
10765
+ return propertyKey && isNodeOfType(propertyKey, "Literal") && typeof propertyKey.value === "string" ? new Set([propertyKey.value]) : null;
10766
+ }
10767
+ };
10768
+ const getLocalCallTarget = (call) => {
10769
+ const callee = stripParenExpression(call.callee);
10770
+ if (isFunctionLike$1(callee)) return callee;
10771
+ if (!isNodeOfType(callee, "Identifier")) return null;
10772
+ const symbol = resolveConstIdentifierAlias(callee, scopes);
10773
+ if (!symbol) return null;
10774
+ if (symbol.kind === "function" && isFunctionLike$1(symbol.declarationNode)) return symbol.declarationNode;
10775
+ if (symbol.kind !== "const" || !symbol.initializer) return null;
10776
+ const initializer = stripParenExpression(symbol.initializer);
10777
+ return isFunctionLike$1(initializer) ? initializer : null;
10778
+ };
10779
+ const calls = [];
10780
+ const eventsBySymbolId = /* @__PURE__ */ new Map();
10781
+ walkAst(scopes.rootScope.node, (node) => {
10782
+ if (isNodeOfType(node, "CallExpression")) {
10783
+ const owner = getExecutionOwner(node);
10784
+ const targetOwner = getLocalCallTarget(node);
10785
+ if (targetOwner && !isStaticallyUnreachable(node, owner)) calls.push({
10786
+ call: node,
10787
+ owner,
10788
+ targetOwner
10789
+ });
10790
+ }
10791
+ if (!isNodeOfType(node, "Identifier")) return;
10792
+ const propertyNames = getMutationPropertyNames(node);
10793
+ if (propertyNames === void 0) return;
10794
+ const symbol = resolveConstIdentifierAlias(node, scopes);
10795
+ if (!symbol) return;
10796
+ const owner = getExecutionOwner(node);
10797
+ if (isStaticallyUnreachable(node, owner)) return;
10798
+ const events = eventsBySymbolId.get(symbol.id) ?? [];
10799
+ events.push({
10800
+ node,
10801
+ owner,
10802
+ propertyNames
10803
+ });
10804
+ eventsBySymbolId.set(symbol.id, events);
10805
+ });
10806
+ const getInvokedOwnersBefore = (checkpoint) => {
10807
+ const checkpointOwner = getExecutionOwner(checkpoint);
10808
+ const checkpointStartIndex = getNodeStartIndex(checkpoint);
10809
+ const invokedOwners = /* @__PURE__ */ new Set();
10810
+ const visitOwner = (owner, cutoffIndex) => {
10811
+ for (const call of calls) {
10812
+ if (call.owner !== owner || getNodeStartIndex(call.call) >= cutoffIndex) continue;
10813
+ if (invokedOwners.has(call.targetOwner)) continue;
10814
+ invokedOwners.add(call.targetOwner);
10815
+ visitOwner(call.targetOwner, Number.POSITIVE_INFINITY);
10816
+ }
10817
+ };
10818
+ visitOwner(checkpointOwner, checkpointStartIndex);
10819
+ if (!isNodeOfType(checkpointOwner, "Program")) visitOwner(scopes.rootScope.node, Number.POSITIVE_INFINITY);
10820
+ return invokedOwners;
10821
+ };
10822
+ const getProgramCutoffIndex = (usageOwner) => {
10823
+ if (isNodeOfType(usageOwner, "Program")) return Number.POSITIVE_INFINITY;
10824
+ const directProgramCall = calls.find((call) => isNodeOfType(call.owner, "Program") && call.targetOwner === usageOwner);
10825
+ return directProgramCall ? getNodeStartIndex(directProgramCall.call) : Number.POSITIVE_INFINITY;
10826
+ };
10827
+ const callsByOwner = /* @__PURE__ */ new Map();
10828
+ for (const call of calls) {
10829
+ const ownerCalls = callsByOwner.get(call.owner) ?? [];
10830
+ ownerCalls.push(call);
10831
+ callsByOwner.set(call.owner, ownerCalls);
10832
+ }
10833
+ const ownerReachabilityCache = /* @__PURE__ */ new WeakMap();
10834
+ const canOwnerReach = (owner, targetOwner) => {
10835
+ const cachedResult = ownerReachabilityCache.get(owner)?.get(targetOwner);
10836
+ if (cachedResult !== void 0) return cachedResult;
10837
+ const pendingOwners = [owner];
10838
+ const visitedOwners = /* @__PURE__ */ new Set();
10839
+ let canReach = false;
10840
+ while (pendingOwners.length > 0) {
10841
+ const currentOwner = pendingOwners.pop();
10842
+ if (!currentOwner || visitedOwners.has(currentOwner)) continue;
10843
+ if (currentOwner === targetOwner) {
10844
+ canReach = true;
10845
+ break;
10846
+ }
10847
+ visitedOwners.add(currentOwner);
10848
+ for (const call of callsByOwner.get(currentOwner) ?? []) pendingOwners.push(call.targetOwner);
10849
+ }
10850
+ const cachedTargets = ownerReachabilityCache.get(owner) ?? /* @__PURE__ */ new WeakMap();
10851
+ cachedTargets.set(targetOwner, canReach);
10852
+ ownerReachabilityCache.set(owner, cachedTargets);
10853
+ return canReach;
10854
+ };
10855
+ const getRepeatedControlFlowAncestors = (node, owner) => {
10856
+ const ancestors = /* @__PURE__ */ new Set();
10857
+ let current = node;
10858
+ while (current?.parent && current !== owner) {
10859
+ const parent = current.parent;
10860
+ const isSingleIterationDoWhile = isNodeOfType(parent, "DoWhileStatement") && isNodeOfType(parent.test, "Literal") && !parent.test.value;
10861
+ const loopBody = isNodeOfType(parent, "ForStatement") || isNodeOfType(parent, "ForInStatement") || isNodeOfType(parent, "ForOfStatement") || isNodeOfType(parent, "WhileStatement") || isNodeOfType(parent, "DoWhileStatement") ? parent.body : null;
10862
+ let bodyStatement = node;
10863
+ while (loopBody && isNodeOfType(loopBody, "BlockStatement") && bodyStatement && bodyStatement.parent !== loopBody) bodyStatement = bodyStatement.parent ?? null;
10864
+ const bodyStatementIndex = loopBody && isNodeOfType(loopBody, "BlockStatement") && bodyStatement ? loopBody.body.findIndex((statement) => statement === bodyStatement) : -1;
10865
+ const hasFollowingLoopExit = Boolean(loopBody && isNodeOfType(loopBody, "BlockStatement") && bodyStatementIndex >= 0 && loopBody.body.slice(bodyStatementIndex + 1).some((statement) => isAbruptCompletionStatement(statement, false)));
10866
+ if (loopBody && !isSingleIterationDoWhile && !hasFollowingLoopExit) ancestors.add(parent);
10867
+ current = parent;
10868
+ }
10869
+ return ancestors;
10870
+ };
10871
+ const nodesShareRepeatedControlFlow = (leftNode, rightNode, owner) => {
10872
+ const leftAncestors = getRepeatedControlFlowAncestors(leftNode, owner);
10873
+ if (leftAncestors.size === 0) return false;
10874
+ return [...getRepeatedControlFlowAncestors(rightNode, owner)].some((ancestor) => leftAncestors.has(ancestor));
10875
+ };
10876
+ const callsReachingOwnerCache = /* @__PURE__ */ new WeakMap();
10877
+ const getCallsReachingOwnerByCaller = (targetOwner) => {
10878
+ const cachedCalls = callsReachingOwnerCache.get(targetOwner);
10879
+ if (cachedCalls) return cachedCalls;
10880
+ const reachingCalls = /* @__PURE__ */ new Map();
10881
+ for (const call of calls) {
10882
+ if (!canOwnerReach(call.targetOwner, targetOwner)) continue;
10883
+ const ownerCalls = reachingCalls.get(call.owner) ?? [];
10884
+ ownerCalls.push(call);
10885
+ reachingCalls.set(call.owner, ownerCalls);
10886
+ }
10887
+ callsReachingOwnerCache.set(targetOwner, reachingCalls);
10888
+ return reachingCalls;
10889
+ };
10890
+ const canMutationReachUsageAcrossCalls = (mutationOwner, usageOwner) => {
10891
+ const mutationCallsByOwner = getCallsReachingOwnerByCaller(mutationOwner);
10892
+ const usageCallsByOwner = getCallsReachingOwnerByCaller(usageOwner);
10893
+ for (const [owner, mutationCalls] of mutationCallsByOwner) {
10894
+ const usageCalls = usageCallsByOwner.get(owner);
10895
+ if (!usageCalls) continue;
10896
+ for (const mutationCall of mutationCalls) for (const usageCall of usageCalls) {
10897
+ if (mutationCall === usageCall) continue;
10898
+ if (getNodeStartIndex(mutationCall.call) < getNodeStartIndex(usageCall.call) || isFunctionLike$1(owner) || nodesShareRepeatedControlFlow(mutationCall.call, usageCall.call, owner)) return true;
10899
+ }
10900
+ }
10901
+ return false;
10902
+ };
10903
+ const isExecutionOrderAmbiguous = (usageNode) => {
10904
+ const usageOwner = getExecutionOwner(usageNode);
10905
+ if (isNodeOfType(usageOwner, "Program")) return false;
10906
+ const reachingProgramCalls = calls.filter((call) => isNodeOfType(call.owner, "Program") && canOwnerReach(call.targetOwner, usageOwner));
10907
+ if (reachingProgramCalls.length === 0) return false;
10908
+ return reachingProgramCalls.length !== 1 || reachingProgramCalls[0]?.targetOwner !== usageOwner;
10909
+ };
10910
+ const isMutationOrderAmbiguous = (symbol, usageNode, relevantPropertyName) => {
10911
+ const usageOwner = getExecutionOwner(usageNode);
10912
+ const usageStartIndex = getNodeStartIndex(usageNode);
10913
+ return (eventsBySymbolId.get(symbol.id) ?? []).some((event) => {
10914
+ if (relevantPropertyName !== null && event.propertyNames !== null && !event.propertyNames.has(relevantPropertyName)) return false;
10915
+ if (event.owner === usageOwner) return getNodeStartIndex(event.node) >= usageStartIndex && (isFunctionLike$1(usageOwner) || nodesShareRepeatedControlFlow(event.node, usageNode, usageOwner));
10916
+ if (isNodeOfType(event.owner, "Program")) return (getCallsReachingOwnerByCaller(usageOwner).get(event.owner) ?? []).some((usageCall) => nodesShareRepeatedControlFlow(event.node, usageCall.call, event.owner));
10917
+ return canOwnerReach(event.owner, usageOwner) || canMutationReachUsageAcrossCalls(event.owner, usageOwner);
10918
+ });
10919
+ };
10920
+ const getEventsBefore = (symbol, usageNode) => {
10921
+ const symbolEvents = eventsBySymbolId.get(symbol.id) ?? [];
10922
+ const mutationEvents = [];
10923
+ const visitOwner = (owner, cutoffIndex, activeOwners, isConditionalPath) => {
10924
+ if (activeOwners.has(owner)) return;
10925
+ const nextActiveOwners = new Set(activeOwners);
10926
+ nextActiveOwners.add(owner);
10927
+ const operations = [...symbolEvents.filter((event) => event.owner === owner).map((event) => ({
10928
+ event,
10929
+ index: getNodeStartIndex(event.node)
10930
+ })), ...calls.filter((call) => call.owner === owner).map((call) => ({
10931
+ call,
10932
+ index: getNodeStartIndex(call.call)
10933
+ }))].sort((left, right) => left.index - right.index);
10934
+ for (const operation of operations) {
10935
+ if (operation.index >= cutoffIndex) break;
10936
+ if ("event" in operation) {
10937
+ mutationEvents.push({
10938
+ isConditional: isConditionalPath || isConditionallyExecuted(operation.event.node, operation.event.owner),
10939
+ node: operation.event.node
10940
+ });
10941
+ continue;
10942
+ }
10943
+ visitOwner(operation.call.targetOwner, Number.POSITIVE_INFINITY, nextActiveOwners, isConditionalPath || isConditionallyExecuted(operation.call.call, operation.call.owner));
10944
+ }
10945
+ };
10946
+ const usageOwner = getExecutionOwner(usageNode);
10947
+ if (!isNodeOfType(usageOwner, "Program")) visitOwner(scopes.rootScope.node, getProgramCutoffIndex(usageOwner), /* @__PURE__ */ new Set(), false);
10948
+ visitOwner(usageOwner, getNodeStartIndex(usageNode), /* @__PURE__ */ new Set(), false);
10949
+ return mutationEvents;
10950
+ };
10951
+ const isMutatedBefore = (symbol, usageNode, relevantPropertyName) => {
10952
+ const events = eventsBySymbolId.get(symbol.id);
10953
+ if (!events) return false;
10954
+ const usageStartIndex = getNodeStartIndex(usageNode);
10955
+ const usageOwner = getExecutionOwner(usageNode);
10956
+ const invokedOwners = getInvokedOwnersBefore(usageNode);
10957
+ return events.some((event) => {
10958
+ if (relevantPropertyName !== null && event.propertyNames !== null && !event.propertyNames.has(relevantPropertyName)) return false;
10959
+ if (event.owner === usageOwner) return getNodeStartIndex(event.node) < usageStartIndex;
10960
+ if (isNodeOfType(event.owner, "Program") && !isNodeOfType(usageOwner, "Program")) return getNodeStartIndex(event.node) < getProgramCutoffIndex(usageOwner);
10961
+ return invokedOwners.has(event.owner);
10962
+ });
10963
+ };
10964
+ const inspector = {
10965
+ getEventsBefore,
10966
+ getOutermostTarget,
10967
+ isGlobalNamespaceMethod,
10968
+ isExecutionOrderAmbiguous,
10969
+ isMutationOrderAmbiguous,
10970
+ isMutatedBefore
10971
+ };
10972
+ inspectorCache.set(scopes, inspector);
10973
+ return inspector;
10974
+ };
10975
+ //#endregion
10976
+ //#region src/plugin/rules/security-scan/utils/get-katex-renderer-provenance.ts
10977
+ const isExpectedModuleName = (actualModuleName, expectedModuleName) => expectedModuleName === "katex" ? actualModuleName === "katex" || actualModuleName.startsWith("katex/") : actualModuleName === expectedModuleName;
10978
+ const isGlobalRequireCall = (node, moduleName, scopes) => {
10979
+ const expression = stripParenExpression(node);
10980
+ if (!isNodeOfType(expression, "CallExpression")) return false;
10981
+ const callee = stripParenExpression(expression.callee);
10982
+ const firstArgument = expression.arguments[0];
10983
+ return Boolean(isNodeOfType(callee, "Identifier") && callee.name === "require" && scopes.isGlobalReference(callee) && firstArgument && isNodeOfType(firstArgument, "Literal") && typeof firstArgument.value === "string" && isExpectedModuleName(firstArgument.value, moduleName));
10984
+ };
10985
+ const isTypeScriptImportEqualsFromModule = (symbol, moduleName) => {
10986
+ if (symbol.kind !== "ts-import-equals") return false;
10987
+ const declaration = symbol.declarationNode;
10988
+ if (!isNodeOfType(declaration, "TSImportEqualsDeclaration")) return false;
10989
+ const moduleReference = declaration.moduleReference;
10990
+ return Boolean(isNodeOfType(moduleReference, "TSExternalModuleReference") && isNodeOfType(moduleReference.expression, "Literal") && typeof moduleReference.expression.value === "string" && isExpectedModuleName(moduleReference.expression.value, moduleName));
10991
+ };
10992
+ const isAwaitedImportFromModule = (node, moduleName) => {
10993
+ const expression = stripParenExpression(node);
10994
+ return Boolean(isNodeOfType(expression, "AwaitExpression") && isNodeOfType(expression.argument, "ImportExpression") && isNodeOfType(expression.argument.source, "Literal") && typeof expression.argument.source.value === "string" && isExpectedModuleName(expression.argument.source.value, moduleName));
10995
+ };
10996
+ const getModuleNamespaceSymbol = (node, moduleName, namespacePropertyName, usageNode, scopes) => {
10997
+ const symbol = resolveConstIdentifierAlias(stripParenExpression(node), scopes);
10998
+ const mutationInspector = getSymbolMutationInspector(scopes);
10999
+ if (!symbol || mutationInspector.isExecutionOrderAmbiguous(usageNode) || mutationInspector.isMutationOrderAmbiguous(symbol, usageNode, namespacePropertyName) || mutationInspector.isMutatedBefore(symbol, usageNode, namespacePropertyName)) return null;
11000
+ const importDeclaration = getImportDeclarationForSymbol(symbol);
11001
+ if (typeof importDeclaration?.source.value === "string" && isExpectedModuleName(importDeclaration.source.value, moduleName)) return isNodeOfType(symbol.declarationNode, "ImportDefaultSpecifier") || isNodeOfType(symbol.declarationNode, "ImportNamespaceSpecifier") || getImportedName(symbol.declarationNode) === "default" ? symbol : null;
11002
+ if (isTypeScriptImportEqualsFromModule(symbol, moduleName)) return symbol;
11003
+ if (symbol.kind !== "const" || !symbol.initializer) return null;
11004
+ const initializer = stripParenExpression(symbol.initializer);
11005
+ if (isGlobalRequireCall(initializer, moduleName, scopes)) return symbol;
11006
+ if (isNodeOfType(initializer, "MemberExpression") && getStaticPropertyName(initializer) === "default" && (isGlobalRequireCall(initializer.object, moduleName, scopes) || isAwaitedImportFromModule(initializer.object, moduleName))) return symbol;
11007
+ if (isAwaitedImportFromModule(initializer, moduleName)) return symbol;
11008
+ return null;
11009
+ };
11010
+ const getNamedImportSymbol = (node, moduleName, importedName, usageNode, scopes) => {
11011
+ const symbol = resolveConstIdentifierAlias(stripParenExpression(node), scopes);
11012
+ if (!symbol) return null;
11013
+ const importDeclaration = getImportDeclarationForSymbol(symbol);
11014
+ const mutationInspector = getSymbolMutationInspector(scopes);
11015
+ if (typeof importDeclaration?.source.value !== "string" || !isExpectedModuleName(importDeclaration.source.value, moduleName) || getImportedName(symbol.declarationNode) !== importedName || mutationInspector.isExecutionOrderAmbiguous(usageNode) || mutationInspector.isMutationOrderAmbiguous(symbol, usageNode, null) || mutationInspector.isMutatedBefore(symbol, usageNode, null)) return null;
11016
+ return symbol;
11017
+ };
11018
+ const isKatexNamespace = (node, usageNode, scopes) => getModuleNamespaceSymbol(node, "katex", "renderToString", usageNode, scopes) !== null || isGlobalRequireCall(node, "katex", scopes);
11019
+ const isKatexNamedRenderer = (node, usageNode, scopes) => {
11020
+ if (getNamedImportSymbol(node, "katex", "renderToString", usageNode, scopes)) return true;
11021
+ const expression = stripParenExpression(node);
11022
+ if (!isNodeOfType(expression, "Identifier")) return false;
11023
+ const symbol = scopes.referenceFor(expression)?.resolvedSymbol;
11024
+ if (!symbol || symbol.kind !== "const" || !symbol.initializer || getSymbolMutationInspector(scopes).isMutatedBefore(symbol, usageNode, null)) return false;
11025
+ const initializer = stripParenExpression(symbol.initializer);
11026
+ const bindingProperty = symbol.bindingIdentifier.parent;
11027
+ if (isNodeOfType(bindingProperty, "Property") && getStaticPropertyKeyName(bindingProperty, { allowComputedString: true }) === "renderToString") return isKatexNamespace(initializer, symbol.declarationNode, scopes);
11028
+ if (isNodeOfType(initializer, "MemberExpression") && getStaticPropertyName(initializer) === "renderToString" && isKatexNamespace(initializer.object, symbol.declarationNode, scopes)) return true;
11029
+ if (isNodeOfType(initializer, "Identifier")) return isKatexNamedRenderer(initializer, symbol.declarationNode, scopes);
11030
+ return false;
11031
+ };
11032
+ const isUnprovenKatexShapedRenderer = (node, scopes) => {
11033
+ const expression = stripParenExpression(node);
11034
+ if (isNodeOfType(expression, "Identifier")) {
11035
+ if (!/katex/i.test(expression.name)) return false;
11036
+ const symbol = scopes.referenceFor(expression)?.resolvedSymbol;
11037
+ return Boolean(symbol && symbol.kind !== "parameter" && symbol.kind !== "let");
11038
+ }
11039
+ if (!isNodeOfType(expression, "MemberExpression")) return false;
11040
+ if (getStaticPropertyName(expression) !== "renderToString") return false;
11041
+ const receiver = stripParenExpression(expression.object);
11042
+ if (!isNodeOfType(receiver, "Identifier") || !/katex/i.test(receiver.name)) return false;
11043
+ const symbol = scopes.referenceFor(receiver)?.resolvedSymbol;
11044
+ if (!symbol) return false;
11045
+ if (symbol.kind === "import") {
11046
+ if (!isExpectedModuleName(String(getImportDeclarationForSymbol(symbol)?.source.value ?? ""), "katex")) return true;
11047
+ const mutationInspector = getSymbolMutationInspector(scopes);
11048
+ if (mutationInspector.isExecutionOrderAmbiguous(expression) || mutationInspector.isMutationOrderAmbiguous(symbol, expression, "renderToString")) return false;
11049
+ return mutationInspector.isMutatedBefore(symbol, expression, "renderToString");
11050
+ }
11051
+ if (symbol.kind === "parameter" || symbol.kind === "let" || symbol.kind === "var") return true;
11052
+ if (symbol.kind !== "const" || !symbol.initializer) return false;
11053
+ const initializer = stripParenExpression(symbol.initializer);
11054
+ if (isNodeOfType(initializer, "ObjectExpression")) return true;
11055
+ if (isNodeOfType(initializer, "CallExpression")) {
11056
+ const callee = stripParenExpression(initializer.callee);
11057
+ return isNodeOfType(callee, "Identifier") && callee.name === "require";
11058
+ }
11059
+ return isNodeOfType(initializer, "AwaitExpression") && isNodeOfType(initializer.argument, "ImportExpression");
11060
+ };
11061
+ //#endregion
11062
+ //#region src/plugin/rules/security-scan/utils/get-katex-options-proof.ts
11063
+ const parameterOptionsProofsByScopes = /* @__PURE__ */ new WeakMap();
11064
+ const isStaticallyDisabledTrustValue = (node, scopes) => {
11065
+ const expression = stripParenExpression(node);
11066
+ if (isNodeOfType(expression, "Identifier")) return expression.name === "undefined" && scopes.isGlobalReference(expression);
11067
+ return isNodeOfType(expression, "Literal") && !expression.value;
11068
+ };
11069
+ const getStaticObjectPropertyValue = (node, expectedPropertyName) => {
11070
+ const expression = stripParenExpression(node);
11071
+ if (!isNodeOfType(expression, "ObjectExpression")) return null;
11072
+ let propertyValue;
11073
+ for (const property of expression.properties) {
11074
+ if (!isNodeOfType(property, "Property")) return null;
11075
+ const propertyName = getStaticPropertyKeyName(property, { allowComputedString: true });
11076
+ if (propertyName === null) return null;
11077
+ if (propertyName !== expectedPropertyName) continue;
11078
+ if (property.kind !== "init") return null;
11079
+ propertyValue = property.value;
11080
+ }
11081
+ return propertyValue;
11082
+ };
11083
+ const getPropertyDescriptorValue = (node) => {
11084
+ const expression = stripParenExpression(node);
11085
+ if (!isNodeOfType(expression, "ObjectExpression")) return null;
11086
+ for (const property of expression.properties) {
11087
+ if (!isNodeOfType(property, "Property")) return null;
11088
+ const propertyName = getStaticPropertyKeyName(property, { allowComputedString: true });
11089
+ if (propertyName === null || propertyName === "get" || propertyName === "set") return null;
11090
+ }
11091
+ return getStaticObjectPropertyValue(expression, "value");
11092
+ };
11093
+ const getTrustStateAfterPropertyDescriptor = (currentState, propertyDescriptor, scopes) => {
11094
+ const propertyValue = getPropertyDescriptorValue(propertyDescriptor);
11095
+ if (propertyValue === null) return "trusted";
11096
+ if (propertyValue === void 0) return currentState;
11097
+ return isStaticallyDisabledTrustValue(propertyValue, scopes) ? "untrusted" : "trusted";
11098
+ };
11099
+ const mergeConditionalTrustStates = (currentState, conditionalState) => {
11100
+ if (currentState === conditionalState) return currentState;
11101
+ if (currentState === "trusted" || conditionalState === "trusted") return "trusted";
11102
+ if (currentState === "unsupported" || conditionalState === "unsupported") return "unsupported";
11103
+ return "untrusted";
11104
+ };
11105
+ const applyTrustMutation = (currentState, eventNode, scopes, visitedSymbolIds) => {
11106
+ const mutationInspector = getSymbolMutationInspector(scopes);
11107
+ const target = mutationInspector.getOutermostTarget(eventNode);
11108
+ const parent = target.parent;
11109
+ if (!parent) return "unsupported";
11110
+ if (isNodeOfType(parent, "AssignmentExpression") && parent.left === target) {
11111
+ if (!isNodeOfType(target, "MemberExpression")) return "unsupported";
11112
+ const propertyName = getStaticPropertyName(target);
11113
+ if (propertyName === null) return "trusted";
11114
+ if (propertyName !== "trust") return currentState;
11115
+ return isStaticallyDisabledTrustValue(parent.right, scopes) ? "untrusted" : "trusted";
11116
+ }
11117
+ if (isNodeOfType(parent, "UnaryExpression") && parent.operator === "delete") {
11118
+ if (!isNodeOfType(target, "MemberExpression")) return "unsupported";
11119
+ const propertyName = getStaticPropertyName(target);
11120
+ if (propertyName === null) return "trusted";
11121
+ return propertyName === "trust" ? "absent" : currentState;
11122
+ }
11123
+ if (isNodeOfType(parent, "UpdateExpression")) {
11124
+ if (!isNodeOfType(target, "MemberExpression")) return "unsupported";
11125
+ const propertyName = getStaticPropertyName(target);
11126
+ return propertyName === "trust" || propertyName === null ? "trusted" : currentState;
11127
+ }
11128
+ if (!isNodeOfType(parent, "CallExpression") || parent.arguments[0] !== target) return "unsupported";
11129
+ if (mutationInspector.isGlobalNamespaceMethod(parent.callee, "Object", OBJECT_PROPERTY_MUTATION_METHOD_NAMES)) {
11130
+ const callee = stripParenExpression(parent.callee);
11131
+ if (!isNodeOfType(callee, "MemberExpression")) return "unsupported";
11132
+ const methodName = getStaticPropertyName(callee);
11133
+ if (methodName === "assign") {
11134
+ let nextState = currentState;
11135
+ for (const source of parent.arguments.slice(1)) {
11136
+ const sourceState = getKatexOptionsTrustState(source, source, scopes, new Set(visitedSymbolIds));
11137
+ if (sourceState !== "absent") nextState = sourceState;
11138
+ }
11139
+ return nextState;
11140
+ }
11141
+ if (methodName === "defineProperties") {
11142
+ const propertyDescriptors = parent.arguments[1];
11143
+ if (!propertyDescriptors) return "unsupported";
11144
+ const trustDescriptor = getStaticObjectPropertyValue(propertyDescriptors, "trust");
11145
+ if (trustDescriptor === null) return "trusted";
11146
+ if (trustDescriptor === void 0) return currentState;
11147
+ return getTrustStateAfterPropertyDescriptor(currentState, trustDescriptor, scopes);
11148
+ }
11149
+ const propertyKey = parent.arguments[1];
11150
+ if (!propertyKey || !isNodeOfType(propertyKey, "Literal") || typeof propertyKey.value !== "string") return "trusted";
11151
+ if (propertyKey.value !== "trust") return currentState;
11152
+ const propertyDescriptor = parent.arguments[2];
11153
+ if (!propertyDescriptor) return "unsupported";
11154
+ return getTrustStateAfterPropertyDescriptor(currentState, propertyDescriptor, scopes);
11155
+ }
11156
+ if (mutationInspector.isGlobalNamespaceMethod(parent.callee, "Reflect", REFLECT_PROPERTY_MUTATION_METHOD_NAMES)) {
11157
+ const callee = stripParenExpression(parent.callee);
11158
+ if (!isNodeOfType(callee, "MemberExpression")) return "unsupported";
11159
+ const methodName = getStaticPropertyName(callee);
11160
+ const propertyKey = parent.arguments[1];
11161
+ if (!propertyKey || !isNodeOfType(propertyKey, "Literal") || typeof propertyKey.value !== "string") return "trusted";
11162
+ if (propertyKey.value !== "trust") return currentState;
11163
+ const propertyValue = parent.arguments[2];
11164
+ if (!propertyValue) return "unsupported";
11165
+ if (methodName === "defineProperty") return getTrustStateAfterPropertyDescriptor(currentState, propertyValue, scopes);
11166
+ return isStaticallyDisabledTrustValue(propertyValue, scopes) ? "untrusted" : "trusted";
11167
+ }
11168
+ return "unsupported";
11169
+ };
11170
+ const getKatexOptionsTrustState = (rawNode, usageNode, scopes, visitedSymbolIds) => {
11171
+ if (rawNode === void 0) return "absent";
11172
+ const node = stripParenExpression(rawNode);
11173
+ if (isNodeOfType(node, "Identifier")) {
11174
+ if (node.name === "undefined" && scopes.isGlobalReference(node)) return "absent";
11175
+ const symbol = resolveConstIdentifierAlias(node, scopes);
11176
+ if (!symbol || symbol.kind !== "const" || !symbol.initializer || visitedSymbolIds.has(symbol.id)) return "unsupported";
11177
+ const nextVisitedSymbolIds = new Set(visitedSymbolIds);
11178
+ nextVisitedSymbolIds.add(symbol.id);
11179
+ const mutationInspector = getSymbolMutationInspector(scopes);
11180
+ if (mutationInspector.isMutationOrderAmbiguous(symbol, usageNode, "trust")) return "unsupported";
11181
+ let trustState = getKatexOptionsTrustState(symbol.initializer, usageNode, scopes, nextVisitedSymbolIds);
11182
+ for (const replayedEvent of mutationInspector.getEventsBefore(symbol, usageNode)) {
11183
+ const nextTrustState = applyTrustMutation(trustState, replayedEvent.node, scopes, nextVisitedSymbolIds);
11184
+ if (replayedEvent.isConditional) trustState = mergeConditionalTrustStates(trustState, nextTrustState);
11185
+ else trustState = nextTrustState;
11186
+ }
11187
+ return trustState;
11188
+ }
11189
+ if (!isNodeOfType(node, "ObjectExpression")) return "unsupported";
11190
+ let trustState = "absent";
11191
+ for (const property of node.properties) {
11192
+ if (isNodeOfType(property, "SpreadElement")) {
11193
+ const spreadState = getKatexOptionsTrustState(property.argument, property.argument, scopes, new Set(visitedSymbolIds));
11194
+ if (spreadState !== "absent") trustState = spreadState === "unsupported" ? "trusted" : spreadState;
11195
+ continue;
11196
+ }
11197
+ if (!isNodeOfType(property, "Property")) {
11198
+ trustState = "trusted";
11199
+ continue;
11200
+ }
11201
+ const propertyName = getStaticPropertyKeyName(property, { allowComputedString: true });
11202
+ if (propertyName === null) {
11203
+ trustState = "trusted";
11204
+ continue;
11205
+ }
11206
+ if (propertyName === "trust") trustState = isStaticallyDisabledTrustValue(property.value, scopes) ? "untrusted" : "trusted";
11207
+ }
11208
+ return trustState;
11209
+ };
11210
+ const setKatexParameterOptionsProofs = (scopes, proofs) => {
11211
+ parameterOptionsProofsByScopes.set(scopes, proofs);
11212
+ };
11213
+ const getKatexOptionsProof = (rawNode, usageNode, scopes, visitedSymbolIds) => {
11214
+ const node = rawNode ? stripParenExpression(rawNode) : void 0;
11215
+ if (node && isNodeOfType(node, "Identifier")) {
11216
+ const parameterSymbol = scopes.referenceFor(node)?.resolvedSymbol;
11217
+ const parameterProof = parameterSymbol ? parameterOptionsProofsByScopes.get(scopes)?.get(parameterSymbol.id) : void 0;
11218
+ if (parameterProof) return parameterProof;
11219
+ }
11220
+ const trustState = getKatexOptionsTrustState(rawNode, usageNode, scopes, visitedSymbolIds);
11221
+ return {
11222
+ isConclusive: trustState !== "unsupported",
11223
+ isSafe: trustState === "absent" || trustState === "untrusted"
11224
+ };
11225
+ };
11226
+ //#endregion
11227
+ //#region src/plugin/rules/security-scan/utils/get-katex-html-proof.ts
11228
+ const SAFE_STATIC_HTML_PROOF = {
11229
+ containsKatex: false,
11230
+ isConclusive: true,
11231
+ isSafe: true,
11232
+ isSafeInAttributeContext: true
11233
+ };
11234
+ const SAFE_HTML_FRAGMENT_PROOF = {
11235
+ containsKatex: false,
11236
+ isConclusive: true,
11237
+ isSafe: true,
11238
+ isSafeInAttributeContext: false
11239
+ };
11240
+ const UNKNOWN_HTML_PROOF = {
11241
+ containsKatex: false,
11242
+ isConclusive: false,
11243
+ isSafe: false,
11244
+ isSafeInAttributeContext: false
11245
+ };
11246
+ const UNSUPPORTED_KATEX_PROOF = {
11247
+ containsKatex: true,
11248
+ isConclusive: false,
11249
+ isSafe: false,
11250
+ isSafeInAttributeContext: false
11251
+ };
11252
+ const UNSAFE_KATEX_PROOF = {
11253
+ containsKatex: true,
11254
+ isConclusive: true,
11255
+ isSafe: false,
11256
+ isSafeInAttributeContext: false
11257
+ };
11258
+ const sourceFilenameByScopes = /* @__PURE__ */ new WeakMap();
11259
+ const crossFileDepthByScopes = /* @__PURE__ */ new WeakMap();
11260
+ const registerKatexProofSource = (scopes, filename, depth) => {
11261
+ sourceFilenameByScopes.set(scopes, filename);
11262
+ crossFileDepthByScopes.set(scopes, depth);
11263
+ };
11264
+ const combineHtmlProofs = (proofs) => ({
11265
+ containsKatex: proofs.some((proof) => proof.containsKatex),
11266
+ isConclusive: proofs.every((proof) => proof.isSafe) ? proofs.filter((proof) => proof.containsKatex).every((proof) => proof.isConclusive) : proofs.some((proof) => proof.containsKatex && proof.isConclusive && !proof.isSafe) || proofs.some((proof) => proof.containsKatex && proof.isConclusive) && proofs.some((proof) => !proof.containsKatex && !proof.isSafe),
11267
+ isSafe: proofs.every((proof) => proof.isSafe),
11268
+ isSafeInAttributeContext: proofs.every((proof) => proof.isSafeInAttributeContext)
11269
+ });
11270
+ const getOrderedObjectPropertyValue = (node, propertyName) => {
11271
+ const expression = stripParenExpression(node);
11272
+ if (!isNodeOfType(expression, "ObjectExpression")) return {
11273
+ isKnown: false,
11274
+ value: null
11275
+ };
11276
+ let isKnown = true;
11277
+ let propertyValue = null;
11278
+ for (const property of expression.properties) {
11279
+ if (!isNodeOfType(property, "Property")) {
11280
+ isKnown = false;
11281
+ propertyValue = null;
11282
+ continue;
11283
+ }
11284
+ const currentPropertyName = getStaticPropertyKeyName(property, { allowComputedString: true });
11285
+ if (currentPropertyName === null) {
11286
+ isKnown = false;
11287
+ propertyValue = null;
11288
+ } else if (currentPropertyName === propertyName) {
11289
+ isKnown = true;
11290
+ propertyValue = property.value;
11291
+ }
11292
+ }
11293
+ return {
11294
+ isKnown,
11295
+ value: propertyValue
11296
+ };
11297
+ };
11298
+ const isReactUseMemo = (node, scopes) => {
11299
+ const expression = stripParenExpression(node);
11300
+ if (isNodeOfType(expression, "Identifier")) {
11301
+ const symbol = resolveConstIdentifierAlias(expression, scopes);
11302
+ return Boolean(symbol && symbol.kind === "import" && getImportDeclarationForSymbol(symbol)?.source.value === "react" && getImportedName(symbol.declarationNode) === "useMemo");
11303
+ }
11304
+ if (!isNodeOfType(expression, "MemberExpression") || getStaticPropertyName(expression) !== "useMemo") return false;
11305
+ const symbol = resolveConstIdentifierAlias(stripParenExpression(expression.object), scopes);
11306
+ return Boolean(symbol && symbol.kind === "import" && getImportDeclarationForSymbol(symbol)?.source.value === "react" && (isNodeOfType(symbol.declarationNode, "ImportDefaultSpecifier") || isNodeOfType(symbol.declarationNode, "ImportNamespaceSpecifier") || getImportedName(symbol.declarationNode) === "default"));
11307
+ };
11308
+ const isAllOpeningAngleBracketsEscaped = (node, scopes) => {
11309
+ let current = stripParenExpression(node);
11310
+ let didEscapeEveryOpeningAngleBracket = false;
11311
+ while (isNodeOfType(current, "CallExpression")) {
11312
+ const callee = stripParenExpression(current.callee);
11313
+ if (!isNodeOfType(callee, "MemberExpression")) return false;
11314
+ const methodName = getStaticPropertyName(callee);
11315
+ if (methodName !== "replace" && methodName !== "replaceAll") return false;
11316
+ const searchValue = current.arguments[0];
11317
+ const replacementValue = current.arguments[1];
11318
+ if (!searchValue || !replacementValue || !isNodeOfType(replacementValue, "Literal") || typeof replacementValue.value !== "string" || replacementValue.value.includes("<") || replacementValue.value.includes("$")) return false;
11319
+ if (isNodeOfType(searchValue, "Literal")) {
11320
+ const regularExpression = "regex" in searchValue ? searchValue.regex : void 0;
11321
+ const replacesLiteralOpeningAngleBracket = methodName === "replaceAll" && searchValue.value === "<";
11322
+ const replacesGlobalOpeningAngleBracketPattern = regularExpression?.pattern === "<" && regularExpression.flags.includes("g");
11323
+ if (replacesLiteralOpeningAngleBracket || replacesGlobalOpeningAngleBracketPattern) didEscapeEveryOpeningAngleBracket = true;
11324
+ }
11325
+ current = stripParenExpression(callee.object);
11326
+ }
11327
+ if (!didEscapeEveryOpeningAngleBracket || !isNodeOfType(current, "Identifier")) return false;
11328
+ return scopes.referenceFor(current)?.resolvedSymbol?.kind === "parameter";
11329
+ };
11330
+ const getSanitizerProof = (node, scopes) => {
11331
+ const callee = stripParenExpression(node.callee);
11332
+ if (isNodeOfType(callee, "MemberExpression") && getStaticPropertyName(callee) === "sanitize") {
11333
+ if (getModuleNamespaceSymbol(callee.object, "dompurify", "sanitize", node, scopes) || getModuleNamespaceSymbol(callee.object, "isomorphic-dompurify", "sanitize", node, scopes)) return SAFE_HTML_FRAGMENT_PROOF;
11334
+ }
11335
+ if (isNodeOfType(callee, "MemberExpression") && getStaticPropertyName(callee) === "escape") {
11336
+ if (getModuleNamespaceSymbol(callee.object, "html-escaper", "escape", node, scopes)) return SAFE_STATIC_HTML_PROOF;
11337
+ }
11338
+ if (getNamedImportSymbol(callee, "html-escaper", "escape", node, scopes)) return SAFE_STATIC_HTML_PROOF;
11339
+ if (getNamedImportSymbol(callee, "dompurify", "sanitize", node, scopes) || getNamedImportSymbol(callee, "isomorphic-dompurify", "sanitize", node, scopes)) return SAFE_HTML_FRAGMENT_PROOF;
11340
+ return null;
11341
+ };
11342
+ const getSafePostTransformProof = (node, receiverProof) => {
11343
+ if (!receiverProof.isSafe) return null;
11344
+ const callee = stripParenExpression(node.callee);
11345
+ if (!isNodeOfType(callee, "MemberExpression")) return null;
11346
+ const methodName = getStaticPropertyName(callee);
11347
+ if ((methodName === "trim" || methodName === "trimEnd" || methodName === "trimStart") && node.arguments.length === 0) return receiverProof;
11348
+ if (methodName !== "replace" && methodName !== "replaceAll") return null;
11349
+ const replacement = node.arguments[1];
11350
+ if (!replacement || !isNodeOfType(replacement, "Literal") || typeof replacement.value !== "string" || replacement.value.includes("<")) return null;
11351
+ return {
11352
+ containsKatex: receiverProof.containsKatex,
11353
+ isConclusive: receiverProof.isConclusive,
11354
+ isSafe: true,
11355
+ isSafeInAttributeContext: receiverProof.isSafeInAttributeContext && !/[&>"']/.test(replacement.value)
11356
+ };
11357
+ };
11358
+ const getTemplateInterpolationContext = (staticPrefix) => {
11359
+ const lowerPrefix = staticPrefix.toLowerCase();
11360
+ for (const tagName of [
11361
+ "script",
11362
+ "style",
11363
+ "textarea",
11364
+ "title"
11365
+ ]) if (lowerPrefix.lastIndexOf(`<${tagName}`) > lowerPrefix.lastIndexOf(`</${tagName}`)) return "raw-text";
11366
+ const lastOpeningAngleIndex = staticPrefix.lastIndexOf("<");
11367
+ if (lastOpeningAngleIndex <= staticPrefix.lastIndexOf(">")) return "text";
11368
+ const currentTagText = staticPrefix.slice(lastOpeningAngleIndex + 1);
11369
+ let openQuote = null;
11370
+ for (let index = 0; index < currentTagText.length; index += 1) {
11371
+ const character = currentTagText[index];
11372
+ if ((character === "\"" || character === "'") && currentTagText[index - 1] !== "\\") {
11373
+ if (openQuote === character) openQuote = null;
11374
+ else if (openQuote === null) openQuote = character;
11375
+ }
11376
+ }
11377
+ return openQuote === null ? "unsafe-tag" : "attribute";
11378
+ };
11379
+ const getTemplateLiteralProof = (node, scopes, visitedSymbolIds, parameterProofs) => {
11380
+ const expressionProofs = node.expressions.map((expression) => getKatexHtmlProof(expression, scopes, new Set(visitedSymbolIds), parameterProofs));
11381
+ let staticPrefix = "";
11382
+ let isSafe = true;
11383
+ for (let expressionIndex = 0; expressionIndex < expressionProofs.length; expressionIndex += 1) {
11384
+ staticPrefix += node.quasis[expressionIndex]?.value.raw ?? "";
11385
+ const context = getTemplateInterpolationContext(staticPrefix);
11386
+ const proof = expressionProofs[expressionIndex] ?? UNKNOWN_HTML_PROOF;
11387
+ if (context === "text") isSafe &&= proof.isSafe;
11388
+ else if (context === "attribute") isSafe &&= proof.isSafeInAttributeContext;
11389
+ else isSafe = false;
11390
+ }
11391
+ return {
11392
+ containsKatex: expressionProofs.some((proof) => proof.containsKatex),
11393
+ isConclusive: isSafe ? expressionProofs.filter((proof) => proof.containsKatex).every((proof) => proof.isConclusive) : expressionProofs.some((proof) => proof.containsKatex && proof.isConclusive && !proof.isSafe) || expressionProofs.some((proof) => proof.containsKatex && proof.isConclusive) && expressionProofs.some((proof) => !proof.containsKatex && !proof.isSafe),
11394
+ isSafe,
11395
+ isSafeInAttributeContext: false
11396
+ };
11397
+ };
11398
+ const isReturnStatementStaticallyUnreachable = (returnStatement, functionBody) => {
11399
+ let current = returnStatement;
11400
+ while (current.parent && current !== functionBody) {
11401
+ const parent = current.parent;
11402
+ if (isNodeOfType(parent, "BlockStatement")) {
11403
+ const statementIndex = parent.body.findIndex((statement) => statement === current);
11404
+ if (statementIndex > 0 && parent.body.slice(0, statementIndex).some((statement) => statementAlwaysExits(statement))) return true;
11405
+ }
11406
+ if (isNodeOfType(parent, "SwitchCase")) {
11407
+ const statementIndex = parent.consequent.findIndex((statement) => statement === current);
11408
+ if (statementIndex > 0 && parent.consequent.slice(0, statementIndex).some((statement) => statementAlwaysExits(statement))) return true;
11409
+ }
11410
+ if (isNodeOfType(parent, "IfStatement") && isNodeOfType(parent.test, "Literal")) {
11411
+ const ifStatementAlternate = parent.alternate;
11412
+ const ifStatementConsequent = parent.consequent;
11413
+ const ifStatementTest = parent.test;
11414
+ const isTruthyTest = Boolean(ifStatementTest.value);
11415
+ if (!isTruthyTest && ifStatementConsequent === current) return true;
11416
+ if (isTruthyTest && ifStatementAlternate === current) return true;
11417
+ }
11418
+ if (isNodeOfType(parent, "WhileStatement") && parent.body === current) {
11419
+ const whileStatementTest = parent.test;
11420
+ if (isNodeOfType(whileStatementTest, "Literal") && !whileStatementTest.value) return true;
11421
+ }
11422
+ if (isNodeOfType(parent, "ForStatement") && parent.body === current) {
11423
+ const forStatementTest = parent.test;
11424
+ if (forStatementTest && isNodeOfType(forStatementTest, "Literal") && !forStatementTest.value) return true;
11425
+ }
11426
+ current = parent;
11427
+ }
11428
+ return false;
11429
+ };
11430
+ const getFunctionHtmlProof = (functionNode, scopes, visitedSymbolIds, parameterProofs = /* @__PURE__ */ new Map()) => {
11431
+ if (!isFunctionLike$1(functionNode)) return UNKNOWN_HTML_PROOF;
11432
+ if (!isNodeOfType(functionNode.body, "BlockStatement")) return getKatexHtmlProof(functionNode.body, scopes, visitedSymbolIds, parameterProofs);
11433
+ const functionBody = functionNode.body;
11434
+ const returnProofs = [];
11435
+ walkAst(functionBody, (child) => {
11436
+ if (child !== functionBody && isFunctionLike$1(child)) return false;
11437
+ if (!isNodeOfType(child, "ReturnStatement")) return;
11438
+ if (isReturnStatementStaticallyUnreachable(child, functionBody)) return false;
11439
+ returnProofs.push(child.argument ? getKatexHtmlProof(child.argument, scopes, new Set(visitedSymbolIds), parameterProofs) : SAFE_STATIC_HTML_PROOF);
11440
+ return false;
11441
+ });
11442
+ return returnProofs.length === 0 ? SAFE_STATIC_HTML_PROOF : combineHtmlProofs(returnProofs);
11443
+ };
11444
+ const getLocalFunctionNode = (node, scopes) => {
11445
+ const expression = stripParenExpression(node);
11446
+ if (!isNodeOfType(expression, "Identifier")) return null;
11447
+ const symbol = resolveConstIdentifierAlias(expression, scopes);
11448
+ if (!symbol || symbol.references.some((reference) => reference.flag !== "read")) return null;
11449
+ if (symbol.kind === "function" && isFunctionLike$1(symbol.declarationNode)) return {
11450
+ functionNode: symbol.declarationNode,
11451
+ symbol
11452
+ };
11453
+ if (symbol.kind !== "const" || !symbol.initializer) return null;
11454
+ const initializer = stripParenExpression(symbol.initializer);
11455
+ return isFunctionLike$1(initializer) ? {
11456
+ functionNode: initializer,
11457
+ symbol
11458
+ } : null;
11459
+ };
11460
+ const getCrossFileFunctionProof = (call, scopes) => {
11461
+ const expression = stripParenExpression(call.callee);
11462
+ if (!isNodeOfType(expression, "Identifier")) return null;
11463
+ const symbol = resolveConstIdentifierAlias(expression, scopes);
11464
+ if (!symbol || symbol.kind !== "import") return null;
11465
+ const importDeclaration = getImportDeclarationForSymbol(symbol);
11466
+ const importedName = getImportedName(symbol.declarationNode);
11467
+ const sourceFilename = sourceFilenameByScopes.get(scopes);
11468
+ const source = importDeclaration?.source.value;
11469
+ const currentDepth = crossFileDepthByScopes.get(scopes) ?? 0;
11470
+ if (!sourceFilename || typeof source !== "string" || !importedName || currentDepth >= 2) return null;
11471
+ const resolved = resolveCrossFileFunctionExportWithFilePath(sourceFilename, source, importedName);
11472
+ if (!resolved || !isFunctionLike$1(resolved.functionNode)) return null;
11473
+ const resolvedScopes = analyzeScopes(resolved.programNode);
11474
+ registerKatexProofSource(resolvedScopes, resolved.filePath, currentDepth + 1);
11475
+ const optionsProofs = /* @__PURE__ */ new Map();
11476
+ for (const [parameterIndex, parameter] of resolved.functionNode.params.entries()) {
11477
+ if (!isNodeOfType(parameter, "ObjectPattern")) continue;
11478
+ const argument = call.arguments[parameterIndex];
11479
+ if (!argument) continue;
11480
+ for (const property of parameter.properties) {
11481
+ if (!isNodeOfType(property, "Property") || !isNodeOfType(property.value, "Identifier")) continue;
11482
+ const propertyName = getStaticPropertyKeyName(property, { allowComputedString: true });
11483
+ if (propertyName === null) continue;
11484
+ const argumentProperty = getOrderedObjectPropertyValue(argument, propertyName);
11485
+ const parameterSymbol = resolvedScopes.symbolFor(property.value);
11486
+ if (!argumentProperty.isKnown || !parameterSymbol || parameterSymbol.references.some((reference) => reference.flag !== "read")) continue;
11487
+ if (argumentProperty.value === null) {
11488
+ optionsProofs.set(parameterSymbol.id, {
11489
+ isConclusive: true,
11490
+ isSafe: true
11491
+ });
11492
+ continue;
11493
+ }
11494
+ optionsProofs.set(parameterSymbol.id, getKatexOptionsProof(argumentProperty.value, call, scopes, /* @__PURE__ */ new Set()));
11495
+ }
11496
+ }
11497
+ setKatexParameterOptionsProofs(resolvedScopes, optionsProofs);
11498
+ return getFunctionHtmlProof(resolved.functionNode, resolvedScopes, /* @__PURE__ */ new Set());
11499
+ };
11500
+ const getKatexCallProof = (node, scopes, visitedSymbolIds, parameterProofs) => {
11501
+ if (!isNodeOfType(node, "CallExpression")) return UNKNOWN_HTML_PROOF;
11502
+ const callee = stripParenExpression(node.callee);
11503
+ if (isNodeOfType(callee, "MemberExpression") && getStaticPropertyName(callee) === "renderToString" && isKatexNamespace(callee.object, node, scopes) || isKatexNamedRenderer(callee, node, scopes)) {
11504
+ const optionsProof = getKatexOptionsProof(node.arguments[1], node, scopes, /* @__PURE__ */ new Set());
11505
+ return {
11506
+ containsKatex: true,
11507
+ isConclusive: optionsProof.isConclusive,
11508
+ isSafe: optionsProof.isSafe,
11509
+ isSafeInAttributeContext: false
11510
+ };
11511
+ }
11512
+ const crossFileFunctionProof = getCrossFileFunctionProof(node, scopes);
11513
+ if (crossFileFunctionProof?.containsKatex) return crossFileFunctionProof;
11514
+ const localFunction = getLocalFunctionNode(callee, scopes);
11515
+ if (localFunction && !visitedSymbolIds.has(localFunction.symbol.id)) {
11516
+ const nextVisitedSymbolIds = new Set(visitedSymbolIds);
11517
+ nextVisitedSymbolIds.add(localFunction.symbol.id);
11518
+ const argumentProofs = node.arguments.map((argument) => {
11519
+ const argumentNode = stripParenExpression(argument);
11520
+ return isFunctionLike$1(argumentNode) ? getFunctionHtmlProof(argumentNode, scopes, new Set(visitedSymbolIds), parameterProofs) : getKatexHtmlProof(argumentNode, scopes, new Set(visitedSymbolIds), parameterProofs);
11521
+ });
11522
+ const localParameterProofs = /* @__PURE__ */ new Map();
11523
+ let hasWrittenKatexParameter = false;
11524
+ if (isFunctionLike$1(localFunction.functionNode)) for (const [parameterIndex, parameter] of localFunction.functionNode.params.entries()) {
11525
+ if (!isNodeOfType(parameter, "Identifier")) continue;
11526
+ const parameterSymbol = scopes.symbolFor(parameter);
11527
+ const argumentProof = argumentProofs[parameterIndex];
11528
+ const isParameterReadOnly = parameterSymbol?.references.every((reference) => reference.flag === "read");
11529
+ if (parameterSymbol && argumentProof && isParameterReadOnly) localParameterProofs.set(parameterSymbol.id, argumentProof);
11530
+ if (argumentProof?.containsKatex && parameterSymbol && !isParameterReadOnly) hasWrittenKatexParameter = true;
11531
+ }
11532
+ const localFunctionProof = getFunctionHtmlProof(localFunction.functionNode, scopes, nextVisitedSymbolIds, localParameterProofs);
11533
+ if (!argumentProofs.some((proof) => proof.containsKatex) || localFunctionProof.containsKatex) return localFunctionProof;
11534
+ return hasWrittenKatexParameter ? UNSUPPORTED_KATEX_PROOF : UNSAFE_KATEX_PROOF;
11535
+ }
11536
+ if (isUnprovenKatexShapedRenderer(callee, scopes)) return {
11537
+ containsKatex: true,
11538
+ isConclusive: true,
11539
+ isSafe: false,
11540
+ isSafeInAttributeContext: false
11541
+ };
11542
+ if (isNodeOfType(callee, "MemberExpression")) {
11543
+ const receiverProof = getKatexHtmlProof(callee.object, scopes, new Set(visitedSymbolIds), parameterProofs);
11544
+ if (receiverProof.containsKatex) {
11545
+ const safeTransformProof = getSafePostTransformProof(node, receiverProof);
11546
+ if (safeTransformProof) return safeTransformProof;
11547
+ return receiverProof.isConclusive ? {
11548
+ containsKatex: true,
11549
+ isConclusive: true,
11550
+ isSafe: false,
11551
+ isSafeInAttributeContext: false
11552
+ } : UNSUPPORTED_KATEX_PROOF;
11553
+ }
11554
+ }
11555
+ const sanitizerProof = getSanitizerProof(node, scopes);
11556
+ if (sanitizerProof) return sanitizerProof;
11557
+ if (isAllOpeningAngleBracketsEscaped(node, scopes)) return SAFE_HTML_FRAGMENT_PROOF;
11558
+ if (isReactUseMemo(callee, scopes)) {
11559
+ const callback = node.arguments[0];
11560
+ if (!callback) return UNKNOWN_HTML_PROOF;
11561
+ const callbackNode = stripParenExpression(callback);
11562
+ if (isFunctionLike$1(callbackNode)) return getFunctionHtmlProof(callbackNode, scopes, new Set(visitedSymbolIds), parameterProofs);
11563
+ const localCallback = getLocalFunctionNode(callbackNode, scopes);
11564
+ if (!localCallback || visitedSymbolIds.has(localCallback.symbol.id)) return UNKNOWN_HTML_PROOF;
11565
+ const nextVisitedSymbolIds = new Set(visitedSymbolIds);
11566
+ nextVisitedSymbolIds.add(localCallback.symbol.id);
11567
+ return getFunctionHtmlProof(localCallback.functionNode, scopes, nextVisitedSymbolIds, parameterProofs);
11568
+ }
11569
+ return node.arguments.some((argument) => {
11570
+ const argumentNode = stripParenExpression(argument);
11571
+ return (isFunctionLike$1(argumentNode) ? getFunctionHtmlProof(argumentNode, scopes, new Set(visitedSymbolIds), parameterProofs) : getKatexHtmlProof(argumentNode, scopes, new Set(visitedSymbolIds), parameterProofs)).containsKatex;
11572
+ }) ? UNSUPPORTED_KATEX_PROOF : UNKNOWN_HTML_PROOF;
11573
+ };
11574
+ const getKatexHtmlProof = (rawNode, scopes, visitedSymbolIds, parameterProofs = /* @__PURE__ */ new Map()) => {
11575
+ const node = stripParenExpression(rawNode);
11576
+ if (isNodeOfType(node, "Literal")) return SAFE_STATIC_HTML_PROOF;
11577
+ if (isNodeOfType(node, "UnaryExpression") && node.operator === "void") return SAFE_STATIC_HTML_PROOF;
11578
+ if (isNodeOfType(node, "Identifier")) {
11579
+ if ((node.name === "undefined" || node.name === "NaN") && scopes.isGlobalReference(node)) return SAFE_STATIC_HTML_PROOF;
11580
+ const symbol = scopes.referenceFor(node)?.resolvedSymbol;
11581
+ const parameterProof = symbol ? parameterProofs.get(symbol.id) : void 0;
11582
+ if (parameterProof) return parameterProof;
11583
+ if (!symbol || symbol.kind !== "const" || !symbol.initializer || visitedSymbolIds.has(symbol.id)) return UNKNOWN_HTML_PROOF;
11584
+ const nextVisitedSymbolIds = new Set(visitedSymbolIds);
11585
+ nextVisitedSymbolIds.add(symbol.id);
11586
+ return getKatexHtmlProof(symbol.initializer, scopes, nextVisitedSymbolIds, parameterProofs);
11587
+ }
11588
+ if (isNodeOfType(node, "CallExpression")) return getKatexCallProof(node, scopes, visitedSymbolIds, parameterProofs);
11589
+ if (isNodeOfType(node, "TemplateLiteral")) return getTemplateLiteralProof(node, scopes, visitedSymbolIds, parameterProofs);
11590
+ if (isNodeOfType(node, "ConditionalExpression")) return combineHtmlProofs([getKatexHtmlProof(node.consequent, scopes, new Set(visitedSymbolIds), parameterProofs), getKatexHtmlProof(node.alternate, scopes, new Set(visitedSymbolIds), parameterProofs)]);
11591
+ if (isNodeOfType(node, "LogicalExpression") && node.operator === "&&") return getKatexHtmlProof(node.right, scopes, visitedSymbolIds, parameterProofs);
11592
+ if (isNodeOfType(node, "BinaryExpression") && node.operator === "+" || isNodeOfType(node, "LogicalExpression")) return combineHtmlProofs([getKatexHtmlProof(node.left, scopes, new Set(visitedSymbolIds), parameterProofs), getKatexHtmlProof(node.right, scopes, new Set(visitedSymbolIds), parameterProofs)]);
11593
+ if (isNodeOfType(node, "SequenceExpression")) {
11594
+ const resultExpression = node.expressions.at(-1);
11595
+ return resultExpression ? getKatexHtmlProof(resultExpression, scopes, visitedSymbolIds, parameterProofs) : UNKNOWN_HTML_PROOF;
11596
+ }
11597
+ return UNKNOWN_HTML_PROOF;
11598
+ };
11599
+ //#endregion
11600
+ //#region src/plugin/rules/security-scan/utils/get-katex-sink-proof-ranges.ts
11601
+ const getDangerouslySetInnerHtmlExpression = (attribute) => {
11602
+ let objectExpression = null;
11603
+ if (isNodeOfType(attribute, "JSXAttribute") && isNodeOfType(attribute.name, "JSXIdentifier") && attribute.name.name === "dangerouslySetInnerHTML" && isNodeOfType(attribute.value, "JSXExpressionContainer") && isNodeOfType(attribute.value.expression, "ObjectExpression")) objectExpression = attribute.value.expression;
11604
+ if (isNodeOfType(attribute, "Property") && getStaticPropertyKeyName(attribute, { allowComputedString: true }) === "dangerouslySetInnerHTML" && isNodeOfType(stripParenExpression(attribute.value), "ObjectExpression")) objectExpression = stripParenExpression(attribute.value);
11605
+ if (!isNodeOfType(objectExpression, "ObjectExpression")) return null;
11606
+ let htmlExpression = null;
11607
+ for (const property of objectExpression.properties) {
11608
+ if (isNodeOfType(property, "SpreadElement")) {
11609
+ htmlExpression = null;
11610
+ continue;
11611
+ }
11612
+ if (!isNodeOfType(property, "Property")) {
11613
+ htmlExpression = null;
11614
+ continue;
11615
+ }
11616
+ const propertyName = getStaticPropertyKeyName(property, { allowComputedString: true });
11617
+ if (propertyName === null) {
11618
+ htmlExpression = null;
11619
+ continue;
11620
+ }
11621
+ if (propertyName === "__html") htmlExpression = property.value;
11622
+ }
11623
+ return htmlExpression;
11624
+ };
11625
+ const collectKatexSinkProofRanges = (fileContent, filename) => {
11626
+ const program = parseSourceText({
11627
+ filename,
11628
+ sourceText: fileContent
11629
+ });
11630
+ if (program === null) return [];
11631
+ const scopes = analyzeScopes(program);
11632
+ registerKatexProofSource(scopes, filename, 0);
11633
+ const ranges = [];
11634
+ walkAst(program, (node) => {
11635
+ const htmlExpression = getDangerouslySetInnerHtmlExpression(node);
11636
+ if (htmlExpression === null) return;
11637
+ const proof = getKatexHtmlProof(htmlExpression, scopes, /* @__PURE__ */ new Set());
11638
+ if (!proof.containsKatex) return;
11639
+ const startIndex = getNodeStartIndex(node);
11640
+ const endIndex = getNodeEndIndex(node);
11641
+ if (startIndex < 0 || endIndex < 0) return;
11642
+ ranges.push({
11643
+ endIndex,
11644
+ proof,
11645
+ startIndex
11646
+ });
11647
+ });
11648
+ return ranges;
11649
+ };
11650
+ //#endregion
10462
11651
  //#region src/plugin/rules/security-scan/dangerous-html-sink.ts
10463
11652
  const DANGEROUS_HTML_PATTERN = /dangerouslySetInnerHTML|(?:\.(?:inner|outer)HTML|\[\s*["'](?:inner|outer)HTML["']\s*\])\s*[+]?=(?!=)|\.insertAdjacentHTML\s*\(|\bdocument\.write(?:ln)?\s*\(|\.(?:createContextualFragment|setHTMLUnsafe)\s*\(/;
10464
11653
  const HTML_VALUE_START_PATTERN = /(?:__html\s*:|(?:\.(?:inner|outer)HTML|\[\s*["'](?:inner|outer)HTML["']\s*\])\s*[+]?=(?!=)|\.insertAdjacentHTML\s*\(\s*[^,]*,|\bdocument\.write(?:ln)?\s*\(|\.(?:createContextualFragment|setHTMLUnsafe)\s*\()\s*([\s\S]*)/;
@@ -10804,6 +11993,7 @@ const dangerousHtmlSink = defineRule({
10804
11993
  if (HIDDEN_TOOLING_DIRECTORY_PATTERN.test(file.relativePath)) return [];
10805
11994
  if (SANITIZER_WRAPPER_PATH_PATTERN.test(file.relativePath)) return [];
10806
11995
  if (!DANGEROUS_HTML_PATTERN.test(file.content)) return [];
11996
+ const katexSinkProofRanges = collectKatexSinkProofRanges(file.content, file.absolutePath);
10807
11997
  const findings = [];
10808
11998
  const lines = file.content.split("\n");
10809
11999
  for (let lineIndex = 0; lineIndex < lines.length; lineIndex += 1) {
@@ -10819,6 +12009,9 @@ const dangerousHtmlSink = defineRule({
10819
12009
  const terminatorIndex = valueTail.search(/[;}]/);
10820
12010
  const valueExpression = terminatorIndex >= 0 ? valueTail.slice(0, terminatorIndex + 1) : valueTail;
10821
12011
  const sinkIndex = lines.slice(0, lineIndex).join("\n").length + (lineIndex > 0 ? 1 : 0) + line.search(DANGEROUS_HTML_PATTERN);
12012
+ const katexSinkProof = katexSinkProofRanges.find((range) => sinkIndex >= range.startIndex && sinkIndex < range.endIndex)?.proof;
12013
+ if (katexSinkProof?.isConclusive && katexSinkProof.isSafe) continue;
12014
+ const hasUnsafeKatexProof = katexSinkProof?.isConclusive === true;
10822
12015
  if (STRING_LITERAL_VALUE_PATTERN.test(valueExpression)) continue;
10823
12016
  if (MODULE_CONSTANT_VALUE_PATTERN.test(valueExpression)) continue;
10824
12017
  if (DOM_CONTENT_SOURCE_VALUE_PATTERN.test(valueExpression) && !valueExpression.includes("+")) {
@@ -10839,21 +12032,21 @@ const dangerousHtmlSink = defineRule({
10839
12032
  if (templateInterpolations === "") continue;
10840
12033
  const judgedExpression = templateInterpolations ?? valueExpression;
10841
12034
  const doesJudgedExpressionCombineValues = splitTopLevelByPlus(judgedExpression).length > 1 || (templateInterpolations?.match(/\$\{/g)?.length ?? 0) > 1;
10842
- if (!doesJudgedExpressionCombineValues && SANITIZER_PATTERN.test(judgedExpression)) continue;
12035
+ if (!hasUnsafeKatexProof && !doesJudgedExpressionCombineValues && SANITIZER_PATTERN.test(judgedExpression)) continue;
10843
12036
  if (!doesJudgedExpressionCombineValues && ENV_CONFIG_VALUE_PATTERN.test(judgedExpression)) continue;
10844
12037
  if (!doesJudgedExpressionCombineValues && I18N_VALUE_PATTERN.test(judgedExpression)) continue;
10845
- if (!isHtmlTainted(judgedExpression, file.content, sinkIndex, /* @__PURE__ */ new Set(), /* @__PURE__ */ new Set())) continue;
10846
- if (ESCAPING_SERIALIZER_CALL_PATTERN.test(valueExpression)) continue;
10847
- if (isTrustedHighlighterValue(valueExpression, file.content, sinkIndex)) continue;
12038
+ if (!hasUnsafeKatexProof && !isHtmlTainted(judgedExpression, file.content, sinkIndex, /* @__PURE__ */ new Set(), /* @__PURE__ */ new Set())) continue;
12039
+ if (!hasUnsafeKatexProof && ESCAPING_SERIALIZER_CALL_PATTERN.test(valueExpression)) continue;
12040
+ if (!hasUnsafeKatexProof && isTrustedHighlighterValue(valueExpression, file.content, sinkIndex)) continue;
10848
12041
  if (valueIdentifier !== void 0) {
10849
12042
  const escapedIdentifier = escapeRegExp(valueIdentifier);
10850
12043
  const visibleDeclaration = findVisibleIdentifierDeclaration(valueIdentifier, sinkIndex, file.content);
10851
12044
  const visibleInitializer = visibleDeclaration?.initializer;
10852
12045
  if (!(visibleInitializer !== void 0 && (splitTopLevelByPlus(visibleInitializer).length > 1 || (visibleInitializer.match(/\$\{/g)?.length ?? 0) > 1))) {
10853
12046
  const fromSerializer = new RegExp(`\\b${escapedIdentifier}\\b\\s*${SERIALIZER_ASSIGNMENT_PATTERN.source}`, "i");
10854
- if (visibleInitializer === void 0 ? fromSerializer.test(file.content) : visibleDeclaration !== null && isDeclarationStable(valueIdentifier, visibleDeclaration, sinkIndex, file.content) && SERIALIZER_CALL_PROVENANCE_PATTERN.test(visibleInitializer)) continue;
12047
+ if (!hasUnsafeKatexProof && (visibleInitializer === void 0 ? fromSerializer.test(file.content) : visibleDeclaration !== null && isDeclarationStable(valueIdentifier, visibleDeclaration, sinkIndex, file.content) && SERIALIZER_CALL_PROVENANCE_PATTERN.test(visibleInitializer))) continue;
10855
12048
  const fromSanitizer = new RegExp(`\\b${escapedIdentifier}\\b\\s*${SANITIZED_ASSIGNMENT_PATTERN.source}`, "i");
10856
- if (visibleInitializer === void 0 ? fromSanitizer.test(file.content) : visibleDeclaration !== null && isDeclarationStable(valueIdentifier, visibleDeclaration, sinkIndex, file.content) && SANITIZED_ASSIGNMENT_PATTERN.test(`=${visibleInitializer}`)) continue;
12049
+ if (!hasUnsafeKatexProof && (visibleInitializer === void 0 ? fromSanitizer.test(file.content) : visibleDeclaration !== null && isDeclarationStable(valueIdentifier, visibleDeclaration, sinkIndex, file.content) && SANITIZED_ASSIGNMENT_PATTERN.test(`=${visibleInitializer}`))) continue;
10857
12050
  }
10858
12051
  if (new RegExp(`\\b${escapedIdentifier}\\b\\s*${DOM_CONTENT_ASSIGNMENT_PATTERN.source}`).test(file.content)) continue;
10859
12052
  if (new RegExp(`highlight[\\w$]*\\s*\\.map\\(\\s*(?:async\\s+)?\\(?\\s*${escapedIdentifier}\\b`, "i").test(file.content) && HIGHLIGHTER_LIBRARY_PATTERN.test(file.content)) continue;
@@ -12543,6 +13736,7 @@ const isNodeReachableWithinFunction = (node, context) => {
12543
13736
  const OBSERVER_REGISTRATION_METHOD_NAME = "observe";
12544
13737
  const CLEANUP_EFFECT_HOOK_NAMES = new Set([...EFFECT_HOOK_NAMES$1, "useInsertionEffect"]);
12545
13738
  const REPLAYABLE_ITERATOR_COLLECTION_CACHE = /* @__PURE__ */ new WeakMap();
13739
+ const REACT_REF_EFFECT_ANALYSIS_CACHE = /* @__PURE__ */ new WeakMap();
12546
13740
  const RESOURCE_NOUN_BY_KIND = {
12547
13741
  subscribe: "subscription",
12548
13742
  timer: "timer",
@@ -13367,7 +14561,7 @@ const hasGuardedDeferredCleanup = (callback, usage, cleanupReturns, context) =>
13367
14561
  const effectHasCleanupForUsage = (callback, usage, context) => {
13368
14562
  if (!isNodeOfType(callback, "ArrowFunctionExpression") && !isNodeOfType(callback, "FunctionExpression")) return false;
13369
14563
  if (callback.async) return false;
13370
- if (usage.kind === "subscribe" && findEnclosingFunction$1(usage.node) === callback && doesResourceResultEscape(usage.node, true) && isCleanupReturningSubscribeLikeCallExpression(usage.node)) return true;
14564
+ if (usage.kind === "subscribe" && findEnclosingFunction$1(usage.node) === callback && doesResourceResultEscape(usage.node, true, true, context) && isCleanupReturningSubscribeLikeCallExpression(usage.node)) return true;
13371
14565
  if (!isNodeOfType(callback.body, "BlockStatement")) return callback.body === usage.node && isCleanupReturningSubscribeLikeCallExpression(callback.body);
13372
14566
  const matchingCleanupReturns = [];
13373
14567
  walkInsideStatementBlocks(callback.body, (child) => {
@@ -13553,6 +14747,8 @@ const isReleaseReachableForUsage = (releaseNode, usage, context) => {
13553
14747
  const releaseFunction = findEnclosingFunction$1(releaseNode);
13554
14748
  if (!releaseFunction) return true;
13555
14749
  if (releaseFunction === findEnclosingFunction$1(usage.node)) return true;
14750
+ const usageFunction = findEnclosingFunction$1(usage.node);
14751
+ if (usageFunction && isFunctionLike$1(usageFunction) && getAssignedReactRefSymbol(usageFunction, context) && isCleanupFunctionReferencedByReturn(usageFunction, releaseFunction, context)) return isReactRefCallbackCleanupOwnedByEffect(usageFunction, releaseFunction, usage, context);
13556
14752
  return isPotentiallyReachableFunction(releaseFunction, context);
13557
14753
  };
13558
14754
  const fileContainsReleaseForUsage = (usage, context) => {
@@ -13793,7 +14989,23 @@ const isUseSyncExternalStoreSubscribeFunction = (functionNode, context) => {
13793
14989
  };
13794
14990
  return isSubscribeBinding(bindingIdentifier);
13795
14991
  };
13796
- const doesResourceResultEscape = (resourceNode, allowConciseReturnEscape) => {
14992
+ const findUnconditionalReturnStatement = (expression, ownerFunction) => {
14993
+ let expressionRoot = findTransparentExpressionRoot(expression);
14994
+ while (isNodeOfType(expressionRoot.parent, "SequenceExpression") && expressionRoot.parent.expressions.at(-1) === expressionRoot) expressionRoot = findTransparentExpressionRoot(expressionRoot.parent);
14995
+ const returnStatement = expressionRoot.parent;
14996
+ return isNodeOfType(returnStatement, "ReturnStatement") && returnStatement.argument === expressionRoot && findEnclosingFunction$1(returnStatement) === ownerFunction ? returnStatement : null;
14997
+ };
14998
+ const getFinalSequenceExpressionValue = (expression) => {
14999
+ let finalExpression = stripParenExpression(expression);
15000
+ while (isNodeOfType(finalExpression, "SequenceExpression")) {
15001
+ const sequenceResult = finalExpression.expressions.at(-1);
15002
+ if (!sequenceResult) break;
15003
+ finalExpression = stripParenExpression(sequenceResult);
15004
+ }
15005
+ return finalExpression;
15006
+ };
15007
+ const doesResourceResultEscape = (resourceNode, allowReturnedResourceEscape, allowConciseReturnEscape, context) => {
15008
+ if (!allowReturnedResourceEscape) return false;
13797
15009
  let currentNode = resourceNode;
13798
15010
  let parentNode = currentNode.parent;
13799
15011
  while (parentNode) {
@@ -13804,22 +15016,33 @@ const doesResourceResultEscape = (resourceNode, allowConciseReturnEscape) => {
13804
15016
  parentNode = currentNode.parent;
13805
15017
  continue;
13806
15018
  }
15019
+ if (isNodeOfType(parentNode, "VariableDeclarator") && parentNode.init === currentNode && isNodeOfType(parentNode.id, "Identifier") && isNodeOfType(parentNode.parent, "VariableDeclaration") && parentNode.parent.kind === "const") {
15020
+ const ownerFunction = findEnclosingFunction$1(resourceNode);
15021
+ const resourceSymbol = context.scopes.symbolFor(parentNode.id);
15022
+ if (!ownerFunction || !resourceSymbol) return false;
15023
+ return doMatchingNodesCoverEveryPathAfterUsage(resourceNode, resourceSymbol.references.flatMap((reference) => {
15024
+ if (reference.flag !== "read") return [];
15025
+ const returnStatement = findUnconditionalReturnStatement(reference.identifier, ownerFunction);
15026
+ return returnStatement ? [returnStatement] : [];
15027
+ }), context);
15028
+ }
13807
15029
  return false;
13808
15030
  }
13809
15031
  return false;
13810
15032
  };
13811
- const findRetainedFunctionLeak = (retainedFunction, context) => {
15033
+ const findRetainedFunctionLeak = (retainedFunction, context, options) => {
13812
15034
  if (!isFunctionLike$1(retainedFunction)) return null;
13813
15035
  const body = retainedFunction.body;
13814
15036
  if (!body) return null;
13815
15037
  let leak = null;
13816
- const allowConciseReturnEscape = !isInlineRetainedHandlerFunction(retainedFunction, context);
15038
+ const allowReturnedResourceEscape = options?.allowReturnedResourceEscape !== false && !retainedFunction.async && !isInlineRetainedHandlerFunction(retainedFunction, context);
15039
+ const allowReturnedSocketEscape = allowReturnedResourceEscape && options?.requireCallableReturnedResource !== true;
13817
15040
  const isExternalStoreSubscribeFunction = isUseSyncExternalStoreSubscribeFunction(retainedFunction, context);
13818
15041
  const hasReleaseForUsage = (usage) => isExternalStoreSubscribeFunction ? effectHasCleanupForUsage(retainedFunction, usage, context) : fileContainsReleaseForUsage(usage, context) || hasGuaranteedRefOwnedUnmountCleanup(retainedFunction, usage, context);
13819
15042
  walkAst(body, (child) => {
13820
15043
  if (leak !== null) return false;
13821
15044
  if (isFunctionLike$1(child)) return false;
13822
- if (isSocketConstruction(child) && !doesResourceResultEscape(child, false)) {
15045
+ if (isSocketConstruction(child) && !doesResourceResultEscape(child, allowReturnedSocketEscape, false, context)) {
13823
15046
  const socketUsage = {
13824
15047
  kind: "socket",
13825
15048
  node: child,
@@ -13836,14 +15059,14 @@ const findRetainedFunctionLeak = (retainedFunction, context) => {
13836
15059
  }
13837
15060
  }
13838
15061
  if (!isNodeOfType(child, "CallExpression")) return;
13839
- if (isNodeOfType(child.callee, "Identifier") && child.callee.name === "setInterval" && !doesResourceResultEscape(child, allowConciseReturnEscape)) {
15062
+ if (isNodeOfType(child.callee, "Identifier") && (child.callee.name === "setInterval" || options?.includeOneShotTimers === true && child.callee.name === "setTimeout" && context.scopes.isGlobalReference(child.callee)) && (options?.allowReturnedTimerEscape === false || !doesResourceResultEscape(child, true, allowReturnedResourceEscape, context))) {
13840
15063
  const timerUsage = {
13841
15064
  kind: "timer",
13842
15065
  node: child,
13843
- resourceName: "setInterval",
15066
+ resourceName: child.callee.name,
13844
15067
  handleKey: findAssignedResourceKey(child, context),
13845
15068
  receiverKey: null,
13846
- registrationVerbName: "setInterval",
15069
+ registrationVerbName: child.callee.name,
13847
15070
  eventKey: null,
13848
15071
  handlerKey: null
13849
15072
  };
@@ -13852,7 +15075,7 @@ const findRetainedFunctionLeak = (retainedFunction, context) => {
13852
15075
  return false;
13853
15076
  }
13854
15077
  }
13855
- if (isSubscribeOrObserveCall(child) && !doesResourceResultEscape(child, allowConciseReturnEscape)) {
15078
+ if (isSubscribeOrObserveCall(child) && (!doesResourceResultEscape(child, allowReturnedResourceEscape, allowReturnedResourceEscape, context) || options?.requireCallableReturnedResource === true && !isCleanupReturningSubscribeLikeCallExpression(child))) {
13856
15079
  const registrationDetails = getCallRegistrationDetails(child, context);
13857
15080
  const subscriptionUsage = {
13858
15081
  kind: "subscribe",
@@ -13867,6 +15090,184 @@ const findRetainedFunctionLeak = (retainedFunction, context) => {
13867
15090
  });
13868
15091
  return leak;
13869
15092
  };
15093
+ const getAssignedReactRefCallbackDefinition = (functionNode, context) => {
15094
+ if (!isFunctionLike$1(functionNode)) return null;
15095
+ if (functionNode.generator) return null;
15096
+ const functionRoot = findTransparentExpressionRoot(functionNode);
15097
+ const assignment = functionRoot.parent;
15098
+ if (!isNodeOfType(assignment, "AssignmentExpression") || assignment.operator !== "=" || assignment.right !== functionRoot) return null;
15099
+ const refSymbol = resolveReactRefSymbol(stripParenExpression(assignment.left), context.scopes);
15100
+ if (!refSymbol) return null;
15101
+ const componentFunction = findRenderPhaseComponentOrHook(assignment, context.scopes);
15102
+ if (!isFunctionLike$1(componentFunction) || findEnclosingFunction$1(assignment) !== componentFunction || findEnclosingFunction$1(refSymbol.bindingIdentifier) !== componentFunction || !isNodeReachableWithinFunction(assignment, context)) return null;
15103
+ return {
15104
+ assignmentNode: assignment,
15105
+ functionNode,
15106
+ refSymbol
15107
+ };
15108
+ };
15109
+ const getAssignedReactRefSymbol = (functionNode, context) => getAssignedReactRefCallbackDefinition(functionNode, context)?.refSymbol ?? null;
15110
+ const isExpressionReturnedFromFunction = (expression, ownerFunction, context) => {
15111
+ let expressionRoot = findTransparentExpressionRoot(expression);
15112
+ const bindingDeclarator = expressionRoot.parent;
15113
+ if (isNodeOfType(bindingDeclarator, "VariableDeclarator") && bindingDeclarator.init === expressionRoot && isNodeOfType(bindingDeclarator.id, "Identifier") && isNodeOfType(bindingDeclarator.parent, "VariableDeclaration") && bindingDeclarator.parent.kind === "const") {
15114
+ const resultSymbol = context.scopes.symbolFor(bindingDeclarator.id);
15115
+ if (!resultSymbol) return false;
15116
+ return doMatchingNodesCoverEveryPathAfterUsage(expression, resultSymbol.references.flatMap((reference) => {
15117
+ if (reference.flag !== "read") return [];
15118
+ const returnStatement = findUnconditionalReturnStatement(reference.identifier, ownerFunction);
15119
+ return returnStatement ? [returnStatement] : [];
15120
+ }), context);
15121
+ }
15122
+ while (true) {
15123
+ const container = expressionRoot.parent;
15124
+ if (isNodeOfType(container, "ConditionalExpression") && (container.consequent === expressionRoot || container.alternate === expressionRoot)) {
15125
+ expressionRoot = findTransparentExpressionRoot(container);
15126
+ continue;
15127
+ }
15128
+ if (isNodeOfType(container, "SequenceExpression") && container.expressions.at(-1) === expressionRoot) {
15129
+ expressionRoot = findTransparentExpressionRoot(container);
15130
+ continue;
15131
+ }
15132
+ if (isNodeOfType(container, "LogicalExpression") && container.right === expressionRoot) {
15133
+ expressionRoot = findTransparentExpressionRoot(container);
15134
+ continue;
15135
+ }
15136
+ break;
15137
+ }
15138
+ const returnStatement = expressionRoot.parent;
15139
+ return Boolean(isNodeOfType(returnStatement, "ReturnStatement") && returnStatement.argument === expressionRoot && findEnclosingFunction$1(returnStatement) === ownerFunction || isNodeOfType(ownerFunction, "ArrowFunctionExpression") && ownerFunction.body === expressionRoot);
15140
+ };
15141
+ const isReactRefCurrentCall = (node, refSymbol, context) => isNodeOfType(node, "CallExpression") && resolveReactRefSymbol(stripParenExpression(node.callee), context.scopes)?.id === refSymbol.id;
15142
+ const collectAssignedReactRefCallbacks = (componentFunction, context) => {
15143
+ const callbackDefinitionsByRefSymbolId = /* @__PURE__ */ new Map();
15144
+ walkAst(componentFunction.body, (child) => {
15145
+ if (!isFunctionLike$1(child)) return;
15146
+ const callbackDefinition = getAssignedReactRefCallbackDefinition(child, context);
15147
+ if (callbackDefinition) {
15148
+ const existingDefinitions = callbackDefinitionsByRefSymbolId.get(callbackDefinition.refSymbol.id) ?? [];
15149
+ existingDefinitions.push(callbackDefinition);
15150
+ callbackDefinitionsByRefSymbolId.set(callbackDefinition.refSymbol.id, existingDefinitions);
15151
+ }
15152
+ return false;
15153
+ });
15154
+ for (const [refSymbolId, callbackDefinitions] of callbackDefinitionsByRefSymbolId) {
15155
+ const activeDefinitions = callbackDefinitions.filter((callbackDefinition) => !doMatchingNodesCoverEveryPathAfterUsage(callbackDefinition.assignmentNode, callbackDefinitions.filter((otherDefinition) => otherDefinition !== callbackDefinition).map((otherDefinition) => otherDefinition.assignmentNode), context));
15156
+ if (activeDefinitions.length === 0) callbackDefinitionsByRefSymbolId.delete(refSymbolId);
15157
+ else callbackDefinitionsByRefSymbolId.set(refSymbolId, activeDefinitions);
15158
+ }
15159
+ return callbackDefinitionsByRefSymbolId;
15160
+ };
15161
+ const collectUndominatedReactRefCalls = (ownerFunction, refSymbol, context) => {
15162
+ if (!isFunctionLike$1(ownerFunction)) return [];
15163
+ const refWrites = [];
15164
+ const refCalls = [];
15165
+ walkAst(ownerFunction.body, (child) => {
15166
+ if (child !== ownerFunction.body && isFunctionLike$1(child)) return false;
15167
+ if (isNodeOfType(child, "AssignmentExpression") && isNodeReachableWithinFunction(child, context) && resolveReactRefSymbol(stripParenExpression(child.left), context.scopes)?.id === refSymbol.id) refWrites.push(child);
15168
+ if (isReactRefCurrentCall(child, refSymbol, context) && isNodeReachableWithinFunction(child, context)) refCalls.push(child);
15169
+ });
15170
+ return refCalls.filter((refCall) => !doMatchingNodesCoverEveryPathBeforeUsage(refCall, refWrites, ownerFunction, context));
15171
+ };
15172
+ const mergeReactRefEffectUsage = (usageByRefSymbolId, refSymbolId, doesEffectOwnResult) => {
15173
+ const existingUsage = usageByRefSymbolId.get(refSymbolId);
15174
+ if (!existingUsage) {
15175
+ usageByRefSymbolId.set(refSymbolId, { doesEffectOwnEveryResult: doesEffectOwnResult });
15176
+ return true;
15177
+ }
15178
+ if (!existingUsage.doesEffectOwnEveryResult || doesEffectOwnResult) return false;
15179
+ existingUsage.doesEffectOwnEveryResult = false;
15180
+ return true;
15181
+ };
15182
+ const collectReactRefEffectAnalysis = (componentFunction, context) => {
15183
+ let analysisByComponent = REACT_REF_EFFECT_ANALYSIS_CACHE.get(context);
15184
+ if (!analysisByComponent) {
15185
+ analysisByComponent = /* @__PURE__ */ new WeakMap();
15186
+ REACT_REF_EFFECT_ANALYSIS_CACHE.set(context, analysisByComponent);
15187
+ }
15188
+ const cachedAnalysis = analysisByComponent.get(componentFunction);
15189
+ if (cachedAnalysis) return cachedAnalysis;
15190
+ const callbackDefinitionsByRefSymbolId = collectAssignedReactRefCallbacks(componentFunction, context);
15191
+ const usageByRefSymbolId = /* @__PURE__ */ new Map();
15192
+ walkAst(componentFunction.body, (child) => {
15193
+ if (child !== componentFunction.body && isFunctionLike$1(child)) return false;
15194
+ if (!isNodeOfType(child, "CallExpression") || findEnclosingFunction$1(child) !== componentFunction || !isReactApiCall(child, CLEANUP_EFFECT_HOOK_NAMES, context.scopes, { allowGlobalReactNamespace: true })) return;
15195
+ const effectCallback = getEffectCallback(child);
15196
+ if (!isFunctionLike$1(effectCallback)) return;
15197
+ for (const callbackDefinitions of callbackDefinitionsByRefSymbolId.values()) {
15198
+ const refSymbol = callbackDefinitions[0]?.refSymbol;
15199
+ if (!refSymbol) continue;
15200
+ for (const refCall of collectUndominatedReactRefCalls(effectCallback, refSymbol, context)) mergeReactRefEffectUsage(usageByRefSymbolId, refSymbol.id, !effectCallback.async && isExpressionReturnedFromFunction(refCall, effectCallback, context));
15201
+ }
15202
+ });
15203
+ let didUsageChange = true;
15204
+ while (didUsageChange) {
15205
+ didUsageChange = false;
15206
+ for (const callbackDefinitions of callbackDefinitionsByRefSymbolId.values()) {
15207
+ const ownerRefSymbol = callbackDefinitions[0]?.refSymbol;
15208
+ if (!ownerRefSymbol) continue;
15209
+ const ownerUsage = usageByRefSymbolId.get(ownerRefSymbol.id);
15210
+ if (!ownerUsage) continue;
15211
+ for (const callbackDefinition of callbackDefinitions) for (const targetDefinitions of callbackDefinitionsByRefSymbolId.values()) {
15212
+ const targetRefSymbol = targetDefinitions[0]?.refSymbol;
15213
+ if (!targetRefSymbol) continue;
15214
+ for (const refCall of collectUndominatedReactRefCalls(callbackDefinition.functionNode, targetRefSymbol, context)) {
15215
+ const doesEffectOwnResult = ownerUsage.doesEffectOwnEveryResult && !callbackDefinition.functionNode.async && isExpressionReturnedFromFunction(refCall, callbackDefinition.functionNode, context);
15216
+ if (mergeReactRefEffectUsage(usageByRefSymbolId, targetRefSymbol.id, doesEffectOwnResult)) didUsageChange = true;
15217
+ }
15218
+ }
15219
+ }
15220
+ }
15221
+ const analysis = {
15222
+ callbackDefinitionsByRefSymbolId,
15223
+ usageByRefSymbolId
15224
+ };
15225
+ analysisByComponent.set(componentFunction, analysis);
15226
+ return analysis;
15227
+ };
15228
+ const getReactRefEffectUsage = (retainedFunction, context) => {
15229
+ if (!isFunctionLike$1(retainedFunction)) return null;
15230
+ const callbackDefinition = getAssignedReactRefCallbackDefinition(retainedFunction, context);
15231
+ const componentFunction = findRenderPhaseComponentOrHook(retainedFunction, context.scopes);
15232
+ if (!callbackDefinition || !isFunctionLike$1(componentFunction)) return null;
15233
+ const analysis = collectReactRefEffectAnalysis(componentFunction, context);
15234
+ if (!analysis.callbackDefinitionsByRefSymbolId.get(callbackDefinition.refSymbol.id)?.some((activeDefinition) => activeDefinition.functionNode === retainedFunction)) return null;
15235
+ return analysis.usageByRefSymbolId.get(callbackDefinition.refSymbol.id) ?? null;
15236
+ };
15237
+ const isReactRefCallbackCleanupOwnedByEffect = (retainedFunction, cleanupFunction, usage, context) => {
15238
+ if (!isFunctionLike$1(retainedFunction) || retainedFunction.async || getReactRefEffectUsage(retainedFunction, context)?.doesEffectOwnEveryResult !== true) return false;
15239
+ if (!isNodeOfType(retainedFunction.body, "BlockStatement")) return false;
15240
+ const doesReturnedCleanupCallFunction = (returnedValue) => {
15241
+ const returnedCleanupFunction = resolveRefOwnedCleanupFunction(getFinalSequenceExpressionValue(returnedValue), context);
15242
+ if (!returnedCleanupFunction) return false;
15243
+ if (returnedCleanupFunction === cleanupFunction) return true;
15244
+ if (!isFunctionLike$1(returnedCleanupFunction)) return false;
15245
+ const matchingCalls = [];
15246
+ walkAst(returnedCleanupFunction.body, (child) => {
15247
+ if (child !== returnedCleanupFunction.body && isFunctionLike$1(child)) return false;
15248
+ if (isNodeOfType(child, "CallExpression") && resolveRefOwnedCleanupFunction(child.callee, context) === cleanupFunction) matchingCalls.push(child);
15249
+ });
15250
+ return doMatchingNodesCoverEveryPathFromFunctionEntry(returnedCleanupFunction, matchingCalls, context);
15251
+ };
15252
+ const matchingReturns = [];
15253
+ walkInsideStatementBlocks(retainedFunction.body, (child) => {
15254
+ if (isNodeOfType(child, "ReturnStatement") && child.argument && doesReturnedCleanupCallFunction(child.argument)) matchingReturns.push(child);
15255
+ });
15256
+ return doMatchingNodesCoverEveryPathAfterUsage(usage.node, matchingReturns, context);
15257
+ };
15258
+ const isCleanupFunctionReferencedByReturn = (ownerFunction, cleanupFunction, context) => {
15259
+ if (!isFunctionLike$1(ownerFunction) || !isNodeOfType(ownerFunction.body, "BlockStatement")) return false;
15260
+ let isReferencedByReturn = false;
15261
+ walkInsideStatementBlocks(ownerFunction.body, (child) => {
15262
+ if (isReferencedByReturn || !isNodeOfType(child, "ReturnStatement") || !child.argument) return;
15263
+ walkAst(child.argument, (returnedChild) => {
15264
+ if (resolveRefOwnedCleanupFunction(returnedChild, context) !== cleanupFunction) return;
15265
+ isReferencedByReturn = true;
15266
+ return false;
15267
+ });
15268
+ });
15269
+ return isReferencedByReturn;
15270
+ };
13870
15271
  const isRetainedComponentScopeFunction = (functionNode) => {
13871
15272
  if (isNodeOfType(functionNode, "FunctionDeclaration")) return enclosingComponentOrHookName(functionNode) !== null;
13872
15273
  if (!isNodeOfType(functionNode, "ArrowFunctionExpression") && !isNodeOfType(functionNode, "FunctionExpression")) return false;
@@ -13901,8 +15302,14 @@ const effectNeedsCleanup = defineRule({
13901
15302
  recommendation: "Return a cleanup function that stops the subscription or timer: `return () => target.removeEventListener(name, handler)` for listeners, `return () => clearInterval(id)` or `clearTimeout(id)` for timers, `return () => observer.disconnect()` for observers, `return () => socket.close()` for connections, or `return unsubscribe` if the subscribe call already gave you one.",
13902
15303
  create: (context) => {
13903
15304
  const reportRetainedLeak = (retainedFunction) => {
13904
- if (!isPotentiallyReachableFunction(retainedFunction, context)) return;
13905
- const leak = findRetainedFunctionLeak(retainedFunction, context);
15305
+ const refEffectUsage = getReactRefEffectUsage(retainedFunction, context);
15306
+ if (!refEffectUsage && !isPotentiallyReachableFunction(retainedFunction, context)) return;
15307
+ const leak = findRetainedFunctionLeak(retainedFunction, context, refEffectUsage ? {
15308
+ allowReturnedResourceEscape: refEffectUsage.doesEffectOwnEveryResult,
15309
+ allowReturnedTimerEscape: false,
15310
+ includeOneShotTimers: true,
15311
+ requireCallableReturnedResource: true
15312
+ } : void 0);
13906
15313
  if (!leak) return;
13907
15314
  const resourceNoun = RESOURCE_NOUN_BY_KIND[leak.kind];
13908
15315
  context.report({
@@ -13935,10 +15342,10 @@ const effectNeedsCleanup = defineRule({
13935
15342
  if (isRetainedComponentScopeFunction(node)) reportRetainedLeak(node);
13936
15343
  },
13937
15344
  ArrowFunctionExpression(node) {
13938
- if (isRetainedComponentScopeFunction(node) || isInlineRetainedHandlerFunction(node, context)) reportRetainedLeak(node);
15345
+ if (isRetainedComponentScopeFunction(node) || isInlineRetainedHandlerFunction(node, context) || getAssignedReactRefSymbol(node, context)) reportRetainedLeak(node);
13939
15346
  },
13940
15347
  FunctionExpression(node) {
13941
- if (isRetainedComponentScopeFunction(node) || isInlineRetainedHandlerFunction(node, context)) reportRetainedLeak(node);
15348
+ if (isRetainedComponentScopeFunction(node) || isInlineRetainedHandlerFunction(node, context) || getAssignedReactRefSymbol(node, context)) reportRetainedLeak(node);
13942
15349
  }
13943
15350
  };
13944
15351
  }
@@ -14401,14 +15808,14 @@ const DEPENDENCY_HOOK_NAMES = new Set([
14401
15808
  "useImperativeHandle",
14402
15809
  "useInsertionEffect"
14403
15810
  ]);
14404
- const crossFileScopes = /* @__PURE__ */ new WeakMap();
15811
+ const crossFileScopes$1 = /* @__PURE__ */ new WeakMap();
14405
15812
  const crossFileControlFlow = /* @__PURE__ */ new WeakMap();
14406
15813
  const forwardedFreshDependencyCache = /* @__PURE__ */ new WeakMap();
14407
- const getCrossFileScopes = (resolved) => {
14408
- const cached = crossFileScopes.get(resolved.programNode);
15814
+ const getCrossFileScopes$1 = (resolved) => {
15815
+ const cached = crossFileScopes$1.get(resolved.programNode);
14409
15816
  if (cached) return cached;
14410
15817
  const scopes = analyzeScopes(resolved.programNode);
14411
- crossFileScopes.set(resolved.programNode, scopes);
15818
+ crossFileScopes$1.set(resolved.programNode, scopes);
14412
15819
  return scopes;
14413
15820
  };
14414
15821
  const getCrossFileControlFlow = (resolved) => {
@@ -14443,7 +15850,7 @@ const isCustomHookFunction = (functionNode, fallbackName) => {
14443
15850
  const displayName = componentOrHookDisplayNameForFunction(functionNode) ?? fallbackName ?? "";
14444
15851
  return /^use[A-Z0-9]/.test(displayName);
14445
15852
  };
14446
- const getImportedHookBinding = (callee, scopes) => {
15853
+ const getImportedHookBinding$1 = (callee, scopes) => {
14447
15854
  if (!isNodeOfType(callee, "Identifier")) return null;
14448
15855
  const importedSymbol = resolveConstIdentifierAlias(callee, scopes);
14449
15856
  if (importedSymbol?.kind !== "import" || !importedSymbol.initializer) return null;
@@ -14459,7 +15866,7 @@ const getImportedHookBinding = (callee, scopes) => {
14459
15866
  };
14460
15867
  const resolveImportedHookFunction = (callee, scopes, currentFilename) => {
14461
15868
  if (!currentFilename) return null;
14462
- const importedBinding = getImportedHookBinding(callee, scopes);
15869
+ const importedBinding = getImportedHookBinding$1(callee, scopes);
14463
15870
  if (!importedBinding) return null;
14464
15871
  const resolved = resolveCrossFileFunctionExportWithFilePath(currentFilename, importedBinding.source, importedBinding.exportedName);
14465
15872
  if (!resolved || !isCustomHookFunction(resolved.functionNode, importedBinding.exportedName)) return null;
@@ -14468,7 +15875,7 @@ const resolveImportedHookFunction = (callee, scopes, currentFilename) => {
14468
15875
  filePath: resolved.filePath,
14469
15876
  functionNode: resolved.functionNode,
14470
15877
  programNode: resolved.programNode,
14471
- scopes: getCrossFileScopes(resolved)
15878
+ scopes: getCrossFileScopes$1(resolved)
14472
15879
  };
14473
15880
  };
14474
15881
  const dependencyIndexForReactHookReference = (expression, scopes, dependencyHookNames, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
@@ -14499,11 +15906,11 @@ const dependencyIndexForReactHookReference = (expression, scopes, dependencyHook
14499
15906
  };
14500
15907
  const getImportedReactDependencyIndex = (callExpression, scopes, currentFilename, dependencyHookNames) => {
14501
15908
  if (!currentFilename) return null;
14502
- const importedBinding = getImportedHookBinding(stripParenExpression(callExpression.callee), scopes);
15909
+ const importedBinding = getImportedHookBinding$1(stripParenExpression(callExpression.callee), scopes);
14503
15910
  if (!importedBinding) return null;
14504
15911
  const resolved = resolveCrossFileValueExportWithFilePath(currentFilename, importedBinding.source, importedBinding.exportedName);
14505
15912
  if (!resolved) return null;
14506
- return dependencyIndexForReactHookReference(resolved.exportedNode, getCrossFileScopes(resolved), dependencyHookNames);
15913
+ return dependencyIndexForReactHookReference(resolved.exportedNode, getCrossFileScopes$1(resolved), dependencyHookNames);
14507
15914
  };
14508
15915
  const resolveHookFunction = (callExpression, scopes, cfg, currentFilename) => {
14509
15916
  const callee = stripParenExpression(callExpression.callee);
@@ -17340,6 +18747,7 @@ const iframeHasTitle = defineRule({
17340
18747
  recommendation: "Add a descriptive `title` so screen reader users know what the embedded frame contains.",
17341
18748
  category: "Accessibility",
17342
18749
  create: (context) => ({ JSXOpeningElement(node) {
18750
+ if (isLocalTestScaffoldJsx(node, context)) return;
17343
18751
  const tag = getElementType(node, context.settings);
17344
18752
  if (tag !== "iframe") return;
17345
18753
  if (isInsideStaticallyHiddenJsxSubtree(node, context.scopes)) return;
@@ -17860,6 +19268,7 @@ const interactiveSupportsFocus = defineRule({
17860
19268
  const settings = resolveSettings$37(context.settings);
17861
19269
  const tabbableSet = new Set(settings.tabbable);
17862
19270
  return { JSXOpeningElement(node) {
19271
+ if (isLocalTestScaffoldJsx(node, context)) return;
17863
19272
  if (node.attributes.length === 0) return;
17864
19273
  if (hasJsxSpreadAttribute$1(node.attributes)) return;
17865
19274
  const roleAttribute = hasJsxPropIgnoreCase(node.attributes, "role");
@@ -18573,7 +19982,7 @@ const scanPerIterationLayoutReads = (body) => {
18573
19982
  hasDeliberateForcedReflow
18574
19983
  };
18575
19984
  };
18576
- const getNodeStart$1 = (node) => {
19985
+ const getNodeStart = (node) => {
18577
19986
  const withRange = node;
18578
19987
  return withRange.range ? withRange.range[0] : -1;
18579
19988
  };
@@ -18605,7 +20014,7 @@ const hasAttachmentBefore = (scopeOwner, elementName, beforeStart) => {
18605
20014
  if (!isNodeOfType(child, "CallExpression")) return;
18606
20015
  const callee = child.callee;
18607
20016
  if (!isNodeOfType(callee, "MemberExpression") || !isNodeOfType(callee.property, "Identifier") || !DOM_ATTACHMENT_METHOD_NAMES.has(callee.property.name)) return;
18608
- if (child.arguments.some((argument) => isNodeOfType(argument, "Identifier") && argument.name === elementName) && getNodeStart$1(child) < beforeStart) {
20017
+ if (child.arguments.some((argument) => isNodeOfType(argument, "Identifier") && argument.name === elementName) && getNodeStart(child) < beforeStart) {
18609
20018
  foundAttachment = true;
18610
20019
  return false;
18611
20020
  }
@@ -18627,7 +20036,7 @@ const isProvablyDetachedAtWrite = (styleWriteStatement) => {
18627
20036
  const elementExpression = assignment.left.object.object;
18628
20037
  const creationRoot = resolveDetachedCreationRoot(elementExpression, 0);
18629
20038
  if (!creationRoot) return false;
18630
- return !hasAttachmentBefore(creationRoot.scopeOwner, creationRoot.rootName, getNodeStart$1(styleWriteStatement));
20039
+ return !hasAttachmentBefore(creationRoot.scopeOwner, creationRoot.rootName, getNodeStart(styleWriteStatement));
18631
20040
  };
18632
20041
  const jsBatchDomCss = defineRule({
18633
20042
  id: "js-batch-dom-css",
@@ -19218,10 +20627,10 @@ const isUncacheableOptionsMergeUtility = (node) => {
19218
20627
  return (argument.properties ?? []).some((property) => isNodeOfType(property, "SpreadElement") && isNodeOfType(property.argument, "Identifier") && parameterNames.has(property.argument.name));
19219
20628
  });
19220
20629
  };
19221
- const isIntlNewExpression = (node) => {
20630
+ const isIntlNewExpression = (node, context) => {
19222
20631
  if (!isNodeOfType(node, "NewExpression")) return false;
19223
20632
  const callee = node.callee;
19224
- if (isNodeOfType(callee, "MemberExpression") && isNodeOfType(callee.object, "Identifier") && callee.object.name === "Intl" && isNodeOfType(callee.property, "Identifier") && INTL_CLASSES.has(callee.property.name)) return true;
20633
+ if (isNodeOfType(callee, "MemberExpression") && isNodeOfType(callee.object, "Identifier") && callee.object.name === "Intl" && context.scopes.isGlobalReference(callee.object) && isNodeOfType(callee.property, "Identifier") && INTL_CLASSES.has(callee.property.name)) return true;
19225
20634
  return false;
19226
20635
  };
19227
20636
  const jsHoistIntl = defineRule({
@@ -19231,7 +20640,7 @@ const jsHoistIntl = defineRule({
19231
20640
  severity: "warn",
19232
20641
  recommendation: "Move `new Intl.NumberFormat(...)` to the top of the file or wrap it in `useMemo`. Building one is slow, so don't redo it on every call",
19233
20642
  create: (context) => ({ NewExpression(node) {
19234
- if (!isIntlNewExpression(node)) return;
20643
+ if (!isIntlNewExpression(node, context)) return;
19235
20644
  let cursor = node.parent ?? null;
19236
20645
  let inFunctionBody = false;
19237
20646
  while (cursor) {
@@ -19350,7 +20759,7 @@ const globSyncReturnsStringPaths = (node, context) => {
19350
20759
  const callee = stripParenExpression(node.callee);
19351
20760
  let isGlobSyncImport = false;
19352
20761
  if (isNodeOfType(callee, "Identifier")) isGlobSyncImport = context.scopes.symbolFor(callee)?.kind === "import" && getImportedNameFromModule(callee, callee.name, "glob") === "globSync";
19353
- else if (isNodeOfType(callee, "MemberExpression") && !callee.computed && isNodeOfType(callee.object, "Identifier") && isNodeOfType(callee.property, "Identifier") && callee.property.name === "globSync") isGlobSyncImport = context.scopes.symbolFor(callee.object)?.kind === "import" && isNamespaceImportFromModule(callee.object, callee.object.name, "glob");
20762
+ else if (isNodeOfType(callee, "MemberExpression") && !callee.computed && isNodeOfType(callee.object, "Identifier") && isNodeOfType(callee.property, "Identifier") && callee.property.name === "globSync") isGlobSyncImport = context.scopes.symbolFor(callee.object)?.kind === "import" && isNamespaceImportFromModule$1(callee.object, callee.object.name, "glob");
19354
20763
  if (!isGlobSyncImport) return false;
19355
20764
  const options = node.arguments[1];
19356
20765
  if (!options) return true;
@@ -19996,6 +21405,28 @@ const jsLengthCheckFirst = defineRule({
19996
21405
  } })
19997
21406
  });
19998
21407
  //#endregion
21408
+ //#region src/plugin/utils/is-proven-global-namespace-reference.ts
21409
+ const isProvenGlobalObjectReference = (expression, scopes, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
21410
+ const strippedExpression = stripParenExpression(expression);
21411
+ if (!isNodeOfType(strippedExpression, "Identifier")) return false;
21412
+ if ((strippedExpression.name === "globalThis" || strippedExpression.name === "window" || strippedExpression.name === "self" || strippedExpression.name === "global") && scopes.isGlobalReference(strippedExpression)) return true;
21413
+ const symbol = scopes.symbolFor(strippedExpression);
21414
+ if (!symbol?.initializer || symbol.kind !== "const" || visitedSymbolIds.has(symbol.id)) return false;
21415
+ visitedSymbolIds.add(symbol.id);
21416
+ return isProvenGlobalObjectReference(symbol.initializer, scopes, visitedSymbolIds);
21417
+ };
21418
+ const isProvenGlobalNamespaceReference = (expression, namespaceName, scopes, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
21419
+ const strippedExpression = stripParenExpression(expression);
21420
+ if (isNodeOfType(strippedExpression, "Identifier")) {
21421
+ if (strippedExpression.name === namespaceName && scopes.isGlobalReference(strippedExpression)) return true;
21422
+ const symbol = scopes.symbolFor(strippedExpression);
21423
+ if (!symbol?.initializer || symbol.kind !== "const" || visitedSymbolIds.has(symbol.id)) return false;
21424
+ visitedSymbolIds.add(symbol.id);
21425
+ return isProvenGlobalNamespaceReference(symbol.initializer, namespaceName, scopes, visitedSymbolIds);
21426
+ }
21427
+ return isNodeOfType(strippedExpression, "MemberExpression") && getStaticPropertyName(strippedExpression) === namespaceName && isProvenGlobalObjectReference(strippedExpression.object, scopes);
21428
+ };
21429
+ //#endregion
19999
21430
  //#region src/plugin/rules/js-performance/js-min-max-loop.ts
20000
21431
  const builtinMutationByProgram = /* @__PURE__ */ new WeakMap();
20001
21432
  const RUNTIMELESS_SYMBOL_KINDS = new Set(["ts-interface", "ts-type-alias"]);
@@ -20052,26 +21483,6 @@ const isSafeFreshNumericArray = (arrayExpression) => {
20052
21483
  }
20053
21484
  return !(didFindPositiveZero && didFindNegativeZero);
20054
21485
  };
20055
- const isGlobalObjectReference = (expression, scopes, visitedSymbols = /* @__PURE__ */ new Set()) => {
20056
- const strippedExpression = stripParenExpression(expression);
20057
- if (!isNodeOfType(strippedExpression, "Identifier")) return false;
20058
- if ((strippedExpression.name === "globalThis" || strippedExpression.name === "window" || strippedExpression.name === "self" || strippedExpression.name === "global") && scopes.isGlobalReference(strippedExpression)) return true;
20059
- const symbol = scopes.symbolFor(strippedExpression);
20060
- if (!symbol?.initializer || symbol.kind !== "const" || visitedSymbols.has(symbol.id)) return false;
20061
- visitedSymbols.add(symbol.id);
20062
- return isGlobalObjectReference(symbol.initializer, scopes, visitedSymbols);
20063
- };
20064
- const resolvesToGlobalNamespace = (expression, namespaceName, scopes, visitedSymbols = /* @__PURE__ */ new Set()) => {
20065
- const strippedExpression = stripParenExpression(expression);
20066
- if (isNodeOfType(strippedExpression, "Identifier")) {
20067
- if (strippedExpression.name === namespaceName && scopes.isGlobalReference(strippedExpression)) return true;
20068
- const symbol = scopes.symbolFor(strippedExpression);
20069
- if (!symbol?.initializer || symbol.kind !== "const" || visitedSymbols.has(symbol.id)) return false;
20070
- visitedSymbols.add(symbol.id);
20071
- return resolvesToGlobalNamespace(symbol.initializer, namespaceName, scopes, visitedSymbols);
20072
- }
20073
- return isNodeOfType(strippedExpression, "MemberExpression") && getStaticPropertyName(strippedExpression) === namespaceName && isGlobalObjectReference(strippedExpression.object, scopes);
20074
- };
20075
21486
  const resolvesToGlobalMethod = (expression, namespaceName, methodNames, scopes, visitedSymbols = /* @__PURE__ */ new Set()) => {
20076
21487
  const strippedExpression = stripParenExpression(expression);
20077
21488
  if (isNodeOfType(strippedExpression, "Identifier")) {
@@ -20080,7 +21491,7 @@ const resolvesToGlobalMethod = (expression, namespaceName, methodNames, scopes,
20080
21491
  visitedSymbols.add(symbol.id);
20081
21492
  return resolvesToGlobalMethod(symbol.initializer, namespaceName, methodNames, scopes, visitedSymbols);
20082
21493
  }
20083
- return isNodeOfType(strippedExpression, "MemberExpression") && methodNames.has(getStaticPropertyName(strippedExpression) ?? "") && resolvesToGlobalNamespace(strippedExpression.object, namespaceName, scopes);
21494
+ return isNodeOfType(strippedExpression, "MemberExpression") && methodNames.has(getStaticPropertyName(strippedExpression) ?? "") && isProvenGlobalNamespaceReference(strippedExpression.object, namespaceName, scopes);
20084
21495
  };
20085
21496
  const resolvesToNativeArrayPrototype = (expression, scopes, visitedSymbols = /* @__PURE__ */ new Set()) => {
20086
21497
  const strippedExpression = stripParenExpression(expression);
@@ -20092,7 +21503,7 @@ const resolvesToNativeArrayPrototype = (expression, scopes, visitedSymbols = /*
20092
21503
  }
20093
21504
  if (isNodeOfType(strippedExpression, "MemberExpression")) {
20094
21505
  const propertyName = getStaticPropertyName(strippedExpression);
20095
- if (propertyName === "prototype") return resolvesToGlobalNamespace(strippedExpression.object, "Array", scopes);
21506
+ if (propertyName === "prototype") return isProvenGlobalNamespaceReference(strippedExpression.object, "Array", scopes);
20096
21507
  return propertyName === "__proto__" && isNodeOfType(stripParenExpression(strippedExpression.object), "ArrayExpression");
20097
21508
  }
20098
21509
  if (!isNodeOfType(strippedExpression, "CallExpression")) return false;
@@ -20103,23 +21514,23 @@ const resolvesToNativeArrayPrototype = (expression, scopes, visitedSymbols = /*
20103
21514
  const isGlobalNamespaceReplacementTarget = (target, namespaceName, scopes) => {
20104
21515
  const strippedTarget = stripParenExpression(target);
20105
21516
  if (isNodeOfType(strippedTarget, "Identifier")) return strippedTarget.name === namespaceName && scopes.isGlobalReference(strippedTarget);
20106
- return isNodeOfType(strippedTarget, "MemberExpression") && getStaticPropertyName(strippedTarget) === namespaceName && isGlobalObjectReference(strippedTarget.object, scopes);
21517
+ return isNodeOfType(strippedTarget, "MemberExpression") && getStaticPropertyName(strippedTarget) === namespaceName && isProvenGlobalObjectReference(strippedTarget.object, scopes);
20107
21518
  };
20108
21519
  const isUnsafeBuiltinMemberTarget = (target, targetFunction, scopes) => {
20109
21520
  const strippedTarget = stripParenExpression(target);
20110
21521
  if (!isNodeOfType(strippedTarget, "MemberExpression")) return false;
20111
21522
  const propertyName = getStaticPropertyName(strippedTarget);
20112
21523
  if (resolvesToNativeArrayPrototype(strippedTarget.object, scopes)) return propertyName === null || propertyName === "sort";
20113
- if (resolvesToGlobalNamespace(strippedTarget.object, "Math", scopes)) return propertyName === null || propertyName === targetFunction;
20114
- return isGlobalObjectReference(strippedTarget.object, scopes) && (propertyName === null || propertyName === "Math");
21524
+ if (isProvenGlobalNamespaceReference(strippedTarget.object, "Math", scopes)) return propertyName === null || propertyName === targetFunction;
21525
+ return isProvenGlobalObjectReference(strippedTarget.object, scopes) && (propertyName === null || propertyName === "Math");
20115
21526
  };
20116
21527
  const isUnsafeBuiltinMutationApiCall = (callExpression, targetFunction, scopes) => {
20117
21528
  const target = callExpression.arguments[0];
20118
21529
  if (!target) return false;
20119
21530
  let propertyName = null;
20120
21531
  if (resolvesToNativeArrayPrototype(target, scopes)) propertyName = "sort";
20121
- else if (resolvesToGlobalNamespace(target, "Math", scopes)) propertyName = targetFunction;
20122
- else if (isGlobalObjectReference(target, scopes)) propertyName = "Math";
21532
+ else if (isProvenGlobalNamespaceReference(target, "Math", scopes)) propertyName = targetFunction;
21533
+ else if (isProvenGlobalObjectReference(target, scopes)) propertyName = "Math";
20123
21534
  if (!propertyName) return false;
20124
21535
  const canObjectExpressionSetProperty = (properties) => {
20125
21536
  if (!isNodeOfType(properties, "ObjectExpression")) return true;
@@ -20170,7 +21581,7 @@ const hasUnsafeMathBinding = (node, scopes) => {
20170
21581
  let scope = scopes.scopeFor(node);
20171
21582
  while (scope) {
20172
21583
  const symbol = scope.symbolsByName.get("Math");
20173
- if (symbol && !RUNTIMELESS_SYMBOL_KINDS.has(symbol.kind)) return !(symbol.kind === "const" && symbol.initializer && resolvesToGlobalNamespace(symbol.initializer, "Math", scopes));
21584
+ if (symbol && !RUNTIMELESS_SYMBOL_KINDS.has(symbol.kind)) return !(symbol.kind === "const" && symbol.initializer && isProvenGlobalNamespaceReference(symbol.initializer, "Math", scopes));
20174
21585
  scope = scope.parent;
20175
21586
  }
20176
21587
  return false;
@@ -25730,6 +27141,7 @@ const mediaHasCaption = defineRule({
25730
27141
  create: (context) => {
25731
27142
  const settings = resolveSettings$23(context.settings);
25732
27143
  return { JSXOpeningElement(node) {
27144
+ if (isLocalTestScaffoldJsx(node, context)) return;
25733
27145
  const tag = getElementType(node, context.settings);
25734
27146
  if (!(settings.audio.has(tag) || settings.video.has(tag))) return;
25735
27147
  if (evaluateMuted(hasJsxPropIgnoreCase(node.attributes, "muted")) === true) return;
@@ -25797,6 +27209,7 @@ const mouseEventsHaveKeyEvents = defineRule({
25797
27209
  create: (context) => {
25798
27210
  const settings = resolveSettings$22(context.settings);
25799
27211
  return { JSXOpeningElement(node) {
27212
+ if (isLocalTestScaffoldJsx(node, context)) return;
25800
27213
  const tag = getElementType(node, context.settings);
25801
27214
  if (!HTML_TAGS.has(tag)) return;
25802
27215
  for (const handler of settings.hoverInHandlers) {
@@ -25838,7 +27251,11 @@ const mouseEventsHaveKeyEvents = defineRule({
25838
27251
  //#region src/plugin/utils/has-directive.ts
25839
27252
  const hasDirective = (programNode, directive) => {
25840
27253
  if (!isNodeOfType(programNode, "Program")) return false;
25841
- return Boolean(programNode.body?.some((statement) => isNodeOfType(statement, "ExpressionStatement") && isNodeOfType(statement.expression, "Literal") && statement.expression.value === directive));
27254
+ for (const statement of programNode.body) {
27255
+ if (!isNodeOfType(statement, "ExpressionStatement") || statement.directive === void 0) return false;
27256
+ if (statement.directive === directive) return true;
27257
+ }
27258
+ return false;
25842
27259
  };
25843
27260
  //#endregion
25844
27261
  //#region src/plugin/rules/nextjs/nextjs-async-client-component.ts
@@ -28467,7 +29884,11 @@ const isProvenNativeReadMethod = (ref, methodName) => Boolean(ref.resolved?.defs
28467
29884
  }));
28468
29885
  //#endregion
28469
29886
  //#region src/plugin/rules/state-and-effects/utils/effect/react.ts
28470
- const KNOWN_PURE_HOC_NAMES = new Set(["memo", "forwardRef"]);
29887
+ const KNOWN_COMPONENT_WRAPPER_NAMES = new Set([
29888
+ "memo",
29889
+ "forwardRef",
29890
+ "observer"
29891
+ ]);
28471
29892
  const startsWithUppercase = (name) => Boolean(name && name.length > 0 && name[0] >= "A" && name[0] <= "Z");
28472
29893
  const isReactFunctionalComponent = (node) => {
28473
29894
  if (!node) return false;
@@ -28489,7 +29910,7 @@ const isReactFunctionalHOC = (analysis, node) => {
28489
29910
  const isWrappedInline = () => {
28490
29911
  if (!isNodeOfType(init, "CallExpression")) return false;
28491
29912
  if (!isNodeOfType(init.callee, "Identifier")) return false;
28492
- if (KNOWN_PURE_HOC_NAMES.has(init.callee.name)) return false;
29913
+ if (KNOWN_COMPONENT_WRAPPER_NAMES.has(init.callee.name)) return false;
28493
29914
  const firstArg = init.arguments?.[0];
28494
29915
  if (!firstArg) return false;
28495
29916
  return isNodeOfType(firstArg, "ArrowFunctionExpression") || isNodeOfType(firstArg, "FunctionExpression");
@@ -28509,7 +29930,7 @@ const isReactFunctionalHOC = (analysis, node) => {
28509
29930
  if (!args.includes(refId)) continue;
28510
29931
  const callee = parent.callee;
28511
29932
  const calleeName = isNodeOfType(callee, "Identifier") ? callee.name : isNodeOfType(callee, "CallExpression") && isNodeOfType(callee.callee, "Identifier") ? callee.callee.name : null;
28512
- if (calleeName != null && !KNOWN_PURE_HOC_NAMES.has(calleeName)) return true;
29933
+ if (calleeName != null && !KNOWN_COMPONENT_WRAPPER_NAMES.has(calleeName)) return true;
28513
29934
  }
28514
29935
  return false;
28515
29936
  };
@@ -31932,7 +33353,7 @@ const isReactDomCreatePortalCall = (node, scopes) => {
31932
33353
  if (!isNodeOfType(callee, "MemberExpression") || callee.computed || !isNodeOfType(callee.object, "Identifier") || !isNodeOfType(callee.property, "Identifier") || callee.property.name !== "createPortal") return false;
31933
33354
  const symbol = scopes.symbolFor(callee.object);
31934
33355
  if (!symbol || symbol.kind !== "import") return false;
31935
- return isDefaultImportFromModule(callee.object, callee.object.name, "react-dom") || isNamespaceImportFromModule(callee.object, callee.object.name, "react-dom");
33356
+ return isDefaultImportFromModule(callee.object, callee.object.name, "react-dom") || isNamespaceImportFromModule$1(callee.object, callee.object.name, "react-dom");
31936
33357
  };
31937
33358
  const containsRenderOutput$1 = (rootNode, scopes) => {
31938
33359
  let hasRenderOutput = false;
@@ -32613,6 +34034,24 @@ const isBuiltinNamespaceCallee = (callee) => {
32613
34034
  }
32614
34035
  return false;
32615
34036
  };
34037
+ const getReactUseCallbackSource = (reference, context) => {
34038
+ const identifier = reference.identifier;
34039
+ const symbol = resolveConstIdentifierAlias(identifier, context.scopes);
34040
+ if (!symbol || symbol.kind !== "const" || !symbol.initializer) return null;
34041
+ const initializer = stripParenExpression(symbol.initializer);
34042
+ if (isNodeOfType(initializer, "CallExpression") && isReactApiCall(initializer, "useCallback", context.scopes, {
34043
+ allowGlobalReactNamespace: true,
34044
+ resolveNamedAliases: true
34045
+ })) return initializer;
34046
+ return null;
34047
+ };
34048
+ const getDependencyStateRefs = (analysis, context, dependencyReference) => {
34049
+ const useCallbackCall = getReactUseCallbackSource(dependencyReference, context);
34050
+ if (!useCallbackCall) return getUpstreamRefs(analysis, dependencyReference).filter((reference) => isState(analysis, reference));
34051
+ const dependencyList = useCallbackCall.arguments?.[1];
34052
+ if (!dependencyList || !isNodeOfType(dependencyList, "ArrayExpression")) return getUpstreamRefs(analysis, dependencyReference).filter((reference) => isState(analysis, reference));
34053
+ return getDownstreamRefs(analysis, dependencyList).flatMap((reference) => getUpstreamRefs(analysis, reference)).filter((reference) => isState(analysis, reference));
34054
+ };
32616
34055
  const isSimpleExpression$1 = (analysis, expression, effectFn, visitedDeclarators) => {
32617
34056
  let isSimple = true;
32618
34057
  walkAst(expression, (child) => {
@@ -32660,7 +34099,7 @@ const noChainStateUpdates = defineRule({
32660
34099
  if (!effectFnRefs || !depsRefs) return;
32661
34100
  const effectFn = getEffectFn(analysis, node);
32662
34101
  if (!effectFn) return;
32663
- const stateDeps = depsRefs.flatMap((ref) => getUpstreamRefs(analysis, ref)).filter((ref) => isState(analysis, ref));
34102
+ const stateDeps = depsRefs.flatMap((reference) => getDependencyStateRefs(analysis, context, reference));
32664
34103
  if (stateDeps.length === 0) return;
32665
34104
  if (stateDeps.every((ref) => isExternallyDrivenState(analysis, ref))) return;
32666
34105
  const stateDepDeclarators = new Set(stateDeps.map((ref) => getUseStateDeclarator(ref)).filter((declarator) => declarator !== null));
@@ -32919,16 +34358,7 @@ const isProvenIntrinsicJsxElement = (openingElement, scopes) => {
32919
34358
  return isIntrinsicValue(openingElement.name);
32920
34359
  };
32921
34360
  //#endregion
32922
- //#region src/plugin/rules/react-builtins/is-safe-create-ref-callback-current-write.ts
32923
- const pathStartsWith$1 = (propertyPath, prefix) => prefix.every((propertyName, index) => propertyPath[index] === propertyName);
32924
- const collectMemberExpression = (identifier) => {
32925
- let expression = findTransparentExpressionRoot(identifier);
32926
- while (expression.parent && isNodeOfType(expression.parent, "MemberExpression") && expression.parent.object === expression) {
32927
- if (!getStaticPropertyName(expression.parent)) return null;
32928
- expression = findTransparentExpressionRoot(expression.parent);
32929
- }
32930
- return expression;
32931
- };
34361
+ //#region src/plugin/utils/is-inline-intrinsic-ref-callback.ts
32932
34362
  const isInlineIntrinsicRefCallback = (functionNode, scopes) => {
32933
34363
  const functionExpression = findTransparentExpressionRoot(functionNode);
32934
34364
  if (!isFunctionLike$1(functionExpression) || functionExpression.async || functionExpression.generator) return false;
@@ -32939,6 +34369,17 @@ const isInlineIntrinsicRefCallback = (functionNode, scopes) => {
32939
34369
  const openingElement = attribute.parent;
32940
34370
  return Boolean(openingElement && isNodeOfType(openingElement, "JSXOpeningElement") && isProvenIntrinsicJsxElement(openingElement, scopes));
32941
34371
  };
34372
+ //#endregion
34373
+ //#region src/plugin/rules/react-builtins/is-safe-create-ref-callback-current-write.ts
34374
+ const pathStartsWith$1 = (propertyPath, prefix) => prefix.every((propertyName, index) => propertyPath[index] === propertyName);
34375
+ const collectMemberExpression = (identifier) => {
34376
+ let expression = findTransparentExpressionRoot(identifier);
34377
+ while (expression.parent && isNodeOfType(expression.parent, "MemberExpression") && expression.parent.object === expression) {
34378
+ if (!getStaticPropertyName(expression.parent)) return null;
34379
+ expression = findTransparentExpressionRoot(expression.parent);
34380
+ }
34381
+ return expression;
34382
+ };
32942
34383
  const isSafeCreateRefCallbackCurrentWrite = (referenceNode, accessedPropertyPath, targetPropertyPath, scopes) => {
32943
34384
  if (accessedPropertyPath.length !== targetPropertyPath.length + 1 || !pathStartsWith$1(accessedPropertyPath, targetPropertyPath) || accessedPropertyPath[targetPropertyPath.length] !== "current") return false;
32944
34385
  const memberExpression = collectMemberExpression(referenceNode);
@@ -33425,6 +34866,151 @@ const isCreateRefResultWriteOnly = (createRefCall, filename, scopes) => {
33425
34866
  return ownedValue ? analyzeSymbolValuePath(ownedValue, state, 12) : analyzeValueUse(createRefCall, [], environment, state, 12);
33426
34867
  };
33427
34868
  //#endregion
34869
+ //#region src/plugin/utils/is-jsx-element-or-fragment.ts
34870
+ /**
34871
+ * Type-guard for the two single-node JSX output forms: `JSXElement`
34872
+ * (`<Foo />`) and `JSXFragment` (`<>…</>`). Canonical home for the
34873
+ * `isNodeOfType(x, "JSXElement") || isNodeOfType(x, "JSXFragment")` check
34874
+ * that many rules otherwise inline. Does NOT unwrap parens / TS wrappers —
34875
+ * callers that need the semantic expression should `stripParenExpression`
34876
+ * first.
34877
+ */
34878
+ const isJsxElementOrFragment = (node) => Boolean(node && (isNodeOfType(node, "JSXElement") || isNodeOfType(node, "JSXFragment")));
34879
+ //#endregion
34880
+ //#region src/plugin/rules/react-builtins/is-proven-one-shot-testing-library-component.ts
34881
+ const REACT_TESTING_LIBRARY_MODULE_SOURCE = "@testing-library/react";
34882
+ const REACT_TESTING_LIBRARY_MODULE_SOURCES = new Set([REACT_TESTING_LIBRARY_MODULE_SOURCE]);
34883
+ const TEST_CALLBACK_NAMES = new Set(["it", "test"]);
34884
+ const TEST_RUNNER_MODULE_SOURCES = new Set(["@jest/globals", "vitest"]);
34885
+ const isNamedImportFromModule = (symbol, importedName, moduleSources) => {
34886
+ if (!symbol || symbol.kind !== "import" || !isNodeOfType(symbol.declarationNode, "ImportSpecifier") || getImportedName(symbol.declarationNode) !== importedName) return false;
34887
+ const importDeclaration = symbol.declarationNode.parent;
34888
+ return Boolean(importDeclaration && isNodeOfType(importDeclaration, "ImportDeclaration") && typeof importDeclaration.source.value === "string" && moduleSources.has(importDeclaration.source.value));
34889
+ };
34890
+ const isNamespaceImportFromModule = (symbol, moduleSource) => {
34891
+ if (!symbol || symbol.kind !== "import" || !isNodeOfType(symbol.declarationNode, "ImportNamespaceSpecifier")) return false;
34892
+ const importDeclaration = symbol.declarationNode.parent;
34893
+ return Boolean(importDeclaration && isNodeOfType(importDeclaration, "ImportDeclaration") && importDeclaration.source.value === moduleSource);
34894
+ };
34895
+ const isProvenTestCallback = (functionNode, scopes) => {
34896
+ const callExpression = functionNode.parent;
34897
+ if (!callExpression || !isNodeOfType(callExpression, "CallExpression") || callExpression.arguments[1] !== functionNode) return false;
34898
+ const callee = stripParenExpression(callExpression.callee);
34899
+ if (!isNodeOfType(callee, "Identifier")) return false;
34900
+ if (TEST_CALLBACK_NAMES.has(callee.name) && scopes.isGlobalReference(callee)) return true;
34901
+ const symbol = scopes.symbolFor(callee);
34902
+ if (!symbol || symbol.kind !== "import") return false;
34903
+ const importedName = getImportedName(symbol.declarationNode);
34904
+ return Boolean(importedName && TEST_CALLBACK_NAMES.has(importedName) && isNamedImportFromModule(symbol, importedName, TEST_RUNNER_MODULE_SOURCES));
34905
+ };
34906
+ const getDirectConstComponentSymbol = (functionNode, scopes) => {
34907
+ const declarator = functionNode.parent;
34908
+ if (!declarator || !isNodeOfType(declarator, "VariableDeclarator") || declarator.init !== functionNode || !isNodeOfType(declarator.id, "Identifier")) return null;
34909
+ const declaration = declarator.parent;
34910
+ if (!declaration || !isNodeOfType(declaration, "VariableDeclaration") || declaration.kind !== "const" || declaration.declarations.length !== 1) return null;
34911
+ const testCallback = findEnclosingFunction$1(declarator);
34912
+ if (!testCallback || !isFunctionLike$1(testCallback) || !isProvenTestCallback(testCallback, scopes) || !isNodeOfType(testCallback.body, "BlockStatement") || declaration.parent !== testCallback.body) return null;
34913
+ return scopes.symbolFor(declarator.id);
34914
+ };
34915
+ const isCreateRefDeclaration = (statement, scopes) => isNodeOfType(statement, "VariableDeclaration") && statement.kind === "const" && statement.declarations.length > 0 && statement.declarations.every((declarator) => {
34916
+ const initializer = declarator.init ? stripParenExpression(declarator.init) : null;
34917
+ return Boolean(isNodeOfType(declarator.id, "Identifier") && initializer && isNodeOfType(initializer, "CallExpression") && isReactApiCall(initializer, "createRef", scopes, {
34918
+ allowGlobalReactNamespace: true,
34919
+ allowUnboundBareCalls: true,
34920
+ resolveNamedAliases: true
34921
+ }));
34922
+ });
34923
+ const isSafeReturnedJsx = (returnStatement) => {
34924
+ if (!isNodeOfType(returnStatement, "ReturnStatement") || !returnStatement.argument) return false;
34925
+ const returnedExpression = stripParenExpression(returnStatement.argument);
34926
+ if (!isJsxElementOrFragment(returnedExpression)) return false;
34927
+ let isSafe = true;
34928
+ walkAst(returnedExpression, (node) => {
34929
+ if (isFunctionLike$1(node)) {
34930
+ isSafe = false;
34931
+ return false;
34932
+ }
34933
+ if (isNodeOfType(node, "AssignmentExpression") || isNodeOfType(node, "AwaitExpression") || isNodeOfType(node, "CallExpression") || isNodeOfType(node, "NewExpression") || isNodeOfType(node, "TaggedTemplateExpression") || isNodeOfType(node, "UpdateExpression") || isNodeOfType(node, "YieldExpression")) {
34934
+ isSafe = false;
34935
+ return false;
34936
+ }
34937
+ });
34938
+ return isSafe;
34939
+ };
34940
+ const hasProvenOneShotComponentBody = (functionNode, scopes) => {
34941
+ if (!isFunctionLike$1(functionNode) || !isNodeOfType(functionNode.body, "BlockStatement")) return false;
34942
+ if (!functionNode.params.every((parameter) => isNodeOfType(parameter, "Identifier"))) return false;
34943
+ const statements = functionNode.body.body;
34944
+ if (statements.length < 2) return false;
34945
+ const returnStatement = statements.at(-1);
34946
+ return Boolean(returnStatement && statements.slice(0, -1).every((statement) => isCreateRefDeclaration(statement, scopes)) && isSafeReturnedJsx(returnStatement));
34947
+ };
34948
+ const isProvenReactStrictModeElement = (jsxElement, scopes) => {
34949
+ const elementName = jsxElement.openingElement.name;
34950
+ if (isNodeOfType(elementName, "JSXIdentifier")) {
34951
+ const symbol = scopes.symbolFor(elementName);
34952
+ return Boolean(symbol && isImportedFromReact(symbol) && getImportedName(symbol.declarationNode) === "StrictMode");
34953
+ }
34954
+ return Boolean(isNodeOfType(elementName, "JSXMemberExpression") && isNodeOfType(elementName.object, "JSXIdentifier") && elementName.property.name === "StrictMode" && isReactNamespaceImport(elementName.object, scopes));
34955
+ };
34956
+ const isWhitespaceJsxChild = (node) => isNodeOfType(node, "JSXText") && node.value.trim().length === 0 || isNodeOfType(node, "JSXExpressionContainer") && isNodeOfType(node.expression, "JSXEmptyExpression");
34957
+ const getRootElementForComponentReference = (identifier, scopes) => {
34958
+ const openingElement = identifier.parent;
34959
+ if (!openingElement || !isNodeOfType(openingElement, "JSXOpeningElement") || openingElement.name !== identifier || !openingElement.selfClosing || openingElement.attributes.length !== 0) return null;
34960
+ const componentElement = openingElement.parent;
34961
+ if (!componentElement || !isNodeOfType(componentElement, "JSXElement")) return null;
34962
+ const strictModeElement = componentElement.parent;
34963
+ if (!strictModeElement || !isNodeOfType(strictModeElement, "JSXElement")) return componentElement;
34964
+ if (strictModeElement.openingElement.attributes.length !== 0 || !isProvenReactStrictModeElement(strictModeElement, scopes)) return null;
34965
+ const renderedChildren = strictModeElement.children.filter((child) => !isWhitespaceJsxChild(child));
34966
+ return renderedChildren.length === 1 && renderedChildren[0] === componentElement ? strictModeElement : null;
34967
+ };
34968
+ const isProvenTestingLibraryRenderCall = (callExpression, scopes) => {
34969
+ const callee = stripParenExpression(callExpression.callee);
34970
+ if (isNodeOfType(callee, "Identifier")) return isNamedImportFromModule(scopes.symbolFor(callee), "render", REACT_TESTING_LIBRARY_MODULE_SOURCES);
34971
+ return Boolean(isNodeOfType(callee, "MemberExpression") && getStaticPropertyName(callee) === "render" && isNodeOfType(callee.object, "Identifier") && isNamespaceImportFromModule(scopes.symbolFor(callee.object), REACT_TESTING_LIBRARY_MODULE_SOURCE));
34972
+ };
34973
+ const isSafeRenderResultBinding = (pattern) => {
34974
+ if (!isNodeOfType(pattern, "ObjectPattern")) return false;
34975
+ return pattern.properties.every((property) => {
34976
+ if (!isNodeOfType(property, "Property") || property.computed) return false;
34977
+ return isNodeOfType(property.value, "Identifier") && getStaticPropertyKeyName(property) !== "rerender";
34978
+ });
34979
+ };
34980
+ const isDirectSafeRenderStatement = (callExpression, testCallback) => {
34981
+ if (!isFunctionLike$1(testCallback) || !isNodeOfType(testCallback.body, "BlockStatement")) return false;
34982
+ const expression = findTransparentExpressionRoot(callExpression);
34983
+ const parent = expression.parent;
34984
+ if (!parent) return false;
34985
+ if (isNodeOfType(parent, "ExpressionStatement")) return parent.parent === testCallback.body;
34986
+ if (!isNodeOfType(parent, "VariableDeclarator") || parent.init !== expression || !isSafeRenderResultBinding(parent.id)) return false;
34987
+ const declaration = parent.parent;
34988
+ return Boolean(declaration && isNodeOfType(declaration, "VariableDeclaration") && declaration.declarations.length === 1 && declaration.parent === testCallback.body);
34989
+ };
34990
+ const getProvenIndependentRenderCall = (componentReference, scopes) => {
34991
+ const rootElement = getRootElementForComponentReference(componentReference, scopes);
34992
+ if (!rootElement) return null;
34993
+ const renderedArgument = findTransparentExpressionRoot(rootElement);
34994
+ const callExpression = renderedArgument.parent;
34995
+ if (!callExpression || !isNodeOfType(callExpression, "CallExpression") || callExpression.arguments.length !== 1 || callExpression.arguments[0] !== renderedArgument || !isProvenTestingLibraryRenderCall(callExpression, scopes)) return null;
34996
+ return callExpression;
34997
+ };
34998
+ const isProvenOneShotTestingLibraryComponent = (functionNode, filename, scopes) => {
34999
+ if (!filename || !isTestlikeFilename(filename) || !hasProvenOneShotComponentBody(functionNode, scopes)) return false;
35000
+ const componentSymbol = getDirectConstComponentSymbol(functionNode, scopes);
35001
+ if (!componentSymbol || componentSymbol.references.length === 0) return false;
35002
+ const testCallback = findEnclosingFunction$1(componentSymbol.bindingIdentifier);
35003
+ if (!testCallback) return false;
35004
+ const renderCalls = /* @__PURE__ */ new Set();
35005
+ for (const reference of componentSymbol.references) {
35006
+ if (reference.flag !== "read") return false;
35007
+ const renderCall = getProvenIndependentRenderCall(reference.identifier, scopes);
35008
+ if (!renderCall || findEnclosingFunction$1(renderCall) !== testCallback || !isDirectSafeRenderStatement(renderCall, testCallback)) return false;
35009
+ renderCalls.add(renderCall);
35010
+ }
35011
+ return renderCalls.size > 0;
35012
+ };
35013
+ //#endregion
33428
35014
  //#region src/plugin/rules/react-builtins/no-create-ref-in-function-component.ts
33429
35015
  const MESSAGE$31 = "`createRef()` may escape or be observed beyond the render that created it, so a later render can replace the ref object and detach the observed one. Hoist a `useRef()` call to the component's unconditional top level instead.";
33430
35016
  const isUseMemoCallbackArgument = (functionNode, scopes) => {
@@ -33438,6 +35024,27 @@ const findEnclosingRenderFunction = (node, scopes) => {
33438
35024
  while (enclosingFunction && isUseMemoCallbackArgument(enclosingFunction, scopes)) enclosingFunction = findEnclosingFunction$1(enclosingFunction);
33439
35025
  return enclosingFunction;
33440
35026
  };
35027
+ const isReactUseStateInitialState = (node, scopes) => {
35028
+ const initialState = findTransparentExpressionRoot(node);
35029
+ const stateCall = initialState.parent;
35030
+ return Boolean(stateCall && isNodeOfType(stateCall, "CallExpression") && stateCall.arguments[0] === initialState && isReactApiCall(stateCall, "useState", scopes, {
35031
+ allowGlobalReactNamespace: true,
35032
+ resolveNamedAliases: true
35033
+ }));
35034
+ };
35035
+ const hasDirectExportWrapper = (declarationNode) => {
35036
+ const parent = declarationNode.parent;
35037
+ if (isNodeOfType(parent, "ExportNamedDeclaration") || isNodeOfType(parent, "ExportDefaultDeclaration")) return true;
35038
+ return Boolean(isNodeOfType(declarationNode, "VariableDeclarator") && (isNodeOfType(parent?.parent, "ExportNamedDeclaration") || isNodeOfType(parent?.parent, "ExportDefaultDeclaration")));
35039
+ };
35040
+ const isFunctionExclusivelyUsedAsReactStateInitializer = (functionNode, scopes) => {
35041
+ if (isReactUseStateInitialState(functionNode, scopes)) return true;
35042
+ const bindingIdentifier = getFunctionBindingIdentifier$1(findTransparentExpressionRoot(functionNode));
35043
+ if (!bindingIdentifier) return false;
35044
+ const bindingSymbol = isNodeOfType(functionNode, "FunctionDeclaration") ? scopes.scopeFor(functionNode).symbolsByName.get(bindingIdentifier.name) : scopes.symbolFor(bindingIdentifier);
35045
+ if (!bindingSymbol || bindingSymbol.kind !== "const" && bindingSymbol.kind !== "function" || hasDirectExportWrapper(bindingSymbol.declarationNode) || bindingSymbol.references.length === 0) return false;
35046
+ return bindingSymbol.references.every((reference) => reference.flag === "read" && isReactUseStateInitialState(reference.identifier, scopes));
35047
+ };
33441
35048
  const noCreateRefInFunctionComponent = defineRule({
33442
35049
  id: "no-create-ref-in-function-component",
33443
35050
  title: "createRef in function component",
@@ -33454,6 +35061,8 @@ const noCreateRefInFunctionComponent = defineRule({
33454
35061
  const displayName = componentOrHookDisplayNameForFunction(enclosingFunction);
33455
35062
  if (!displayName) return;
33456
35063
  if (!(isReactHookName(displayName) || functionContainsReactRenderOutput(enclosingFunction, context.scopes, context.cfg))) return;
35064
+ if (isReactUseStateInitialState(node, context.scopes) || isFunctionExclusivelyUsedAsReactStateInitializer(enclosingFunction, context.scopes)) return;
35065
+ if (isProvenOneShotTestingLibraryComponent(enclosingFunction, context.filename, context.scopes)) return;
33457
35066
  if (isCreateRefResultWriteOnly(node, context.filename, context.scopes)) return;
33458
35067
  context.report({
33459
35068
  node,
@@ -34723,7 +36332,6 @@ const isSetStateCallInLifecycle = (setStateCall, lifecycleNames, options = {}) =
34723
36332
  //#region src/plugin/rules/react-builtins/no-did-mount-set-state.ts
34724
36333
  const LIFECYCLE_NAMES$2 = new Set(["componentDidMount"]);
34725
36334
  const MESSAGE$28 = "Your users see an extra render right after mount when you call `setState` in `componentDidMount`.";
34726
- const getNodeStart = (node) => "start" in node && typeof node.start === "number" ? node.start : -1;
34727
36335
  const getEnclosingLifecycleFunction = (setStateCall) => {
34728
36336
  let ancestor = setStateCall.parent;
34729
36337
  while (ancestor) {
@@ -34812,13 +36420,13 @@ const argumentDerivesFromPostMountSource = (setStateCall, lifecycleFunction) =>
34812
36420
  };
34813
36421
  const isAfterAwaitInAsyncLifecycle = (setStateCall, lifecycleFunction) => {
34814
36422
  if (!isFunctionLike$1(lifecycleFunction) || lifecycleFunction.async !== true) return false;
34815
- const callStart = getNodeStart(setStateCall);
36423
+ const callStart = getNodeStartIndex(setStateCall);
34816
36424
  if (callStart < 0) return false;
34817
36425
  let didFindPrecedingAwait = false;
34818
36426
  walkAst(lifecycleFunction, (descendant) => {
34819
36427
  if (didFindPrecedingAwait) return false;
34820
36428
  if (!isNodeOfType(descendant, "AwaitExpression")) return;
34821
- const awaitStart = getNodeStart(descendant);
36429
+ const awaitStart = getNodeStartIndex(descendant);
34822
36430
  if (awaitStart >= 0 && awaitStart < callStart) {
34823
36431
  didFindPrecedingAwait = true;
34824
36432
  return false;
@@ -35882,17 +37490,178 @@ const visitSynchronousFunctionBodies = (analysisFunctions, visitor) => {
35882
37490
  walkInsideStatementBlocks(analysisFunction.body, visitor);
35883
37491
  }
35884
37492
  };
35885
- const collectWrittenStateNamesInEffect = (analysisFunctions, setterToStateName) => {
35886
- const writtenStateNames = /* @__PURE__ */ new Set();
37493
+ const readStaticEffectValue = (expression, scopes, stateSymbolId, stateValue, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
37494
+ const unwrappedExpression = stripParenExpression(expression);
37495
+ if (isNodeOfType(unwrappedExpression, "Literal")) {
37496
+ const literalValue = unwrappedExpression.value;
37497
+ if (literalValue === null || typeof literalValue === "boolean" || typeof literalValue === "number" || typeof literalValue === "string") return { value: literalValue };
37498
+ return null;
37499
+ }
37500
+ if (isNodeOfType(unwrappedExpression, "Identifier")) {
37501
+ if (scopes.symbolFor(unwrappedExpression)?.id === stateSymbolId) return stateValue;
37502
+ if (unwrappedExpression.name === "undefined" && scopes.isGlobalReference(unwrappedExpression)) return { value: void 0 };
37503
+ const immutableSymbol = scopes.symbolFor(unwrappedExpression);
37504
+ if (immutableSymbol?.kind !== "const" || !immutableSymbol.initializer || !isNodeOfType(immutableSymbol.declarationNode, "VariableDeclarator") || immutableSymbol.declarationNode.id !== immutableSymbol.bindingIdentifier || immutableSymbol.declarationNode.init !== immutableSymbol.initializer || immutableSymbol.references.some((reference) => reference.flag !== "read") || visitedSymbolIds.has(immutableSymbol.id)) return null;
37505
+ return readStaticEffectValue(immutableSymbol.initializer, scopes, stateSymbolId, stateValue, new Set(visitedSymbolIds).add(immutableSymbol.id));
37506
+ }
37507
+ if (isNodeOfType(unwrappedExpression, "UnaryExpression")) {
37508
+ if (unwrappedExpression.operator === "void") return { value: void 0 };
37509
+ if (unwrappedExpression.operator !== "!") return null;
37510
+ const argumentValue = readStaticEffectValue(unwrappedExpression.argument, scopes, stateSymbolId, stateValue, visitedSymbolIds);
37511
+ return argumentValue ? { value: !argumentValue.value } : null;
37512
+ }
37513
+ if (isNodeOfType(unwrappedExpression, "CallExpression")) {
37514
+ if (isNodeOfType(unwrappedExpression.callee, "Identifier") && unwrappedExpression.callee.name === "Boolean" && scopes.isGlobalReference(unwrappedExpression.callee) && unwrappedExpression.arguments.length === 1 && unwrappedExpression.arguments[0] && !isNodeOfType(unwrappedExpression.arguments[0], "SpreadElement")) {
37515
+ const argumentValue = readStaticEffectValue(unwrappedExpression.arguments[0], scopes, stateSymbolId, stateValue, visitedSymbolIds);
37516
+ return argumentValue ? { value: Boolean(argumentValue.value) } : null;
37517
+ }
37518
+ return null;
37519
+ }
37520
+ if (isNodeOfType(unwrappedExpression, "LogicalExpression")) {
37521
+ const leftValue = readStaticEffectValue(unwrappedExpression.left, scopes, stateSymbolId, stateValue, visitedSymbolIds);
37522
+ if (!leftValue) return null;
37523
+ if (unwrappedExpression.operator === "&&" && !leftValue.value) return leftValue;
37524
+ if (unwrappedExpression.operator === "||" && leftValue.value) return leftValue;
37525
+ if (unwrappedExpression.operator === "??" && leftValue.value !== null && leftValue.value !== void 0) return leftValue;
37526
+ return readStaticEffectValue(unwrappedExpression.right, scopes, stateSymbolId, stateValue, visitedSymbolIds);
37527
+ }
37528
+ if (isNodeOfType(unwrappedExpression, "ConditionalExpression")) {
37529
+ const testValue = readStaticEffectValue(unwrappedExpression.test, scopes, stateSymbolId, stateValue, visitedSymbolIds);
37530
+ if (!testValue) return null;
37531
+ return readStaticEffectValue(testValue.value ? unwrappedExpression.consequent : unwrappedExpression.alternate, scopes, stateSymbolId, stateValue, visitedSymbolIds);
37532
+ }
37533
+ if (isNodeOfType(unwrappedExpression, "MemberExpression") && unwrappedExpression.optional) {
37534
+ const objectValue = readStaticEffectValue(unwrappedExpression.object, scopes, stateSymbolId, stateValue, visitedSymbolIds);
37535
+ if (objectValue?.value === null || objectValue?.value === void 0) return { value: void 0 };
37536
+ return null;
37537
+ }
37538
+ if (isNodeOfType(unwrappedExpression, "BinaryExpression")) {
37539
+ const leftValue = readStaticEffectValue(unwrappedExpression.left, scopes, stateSymbolId, stateValue, visitedSymbolIds);
37540
+ const rightValue = readStaticEffectValue(unwrappedExpression.right, scopes, stateSymbolId, stateValue, visitedSymbolIds);
37541
+ if (!leftValue || !rightValue) return null;
37542
+ if (unwrappedExpression.operator === "===" || unwrappedExpression.operator === "!==") {
37543
+ const areEqual = leftValue.value === rightValue.value;
37544
+ return { value: unwrappedExpression.operator === "===" ? areEqual : !areEqual };
37545
+ }
37546
+ if (unwrappedExpression.operator === "==" || unwrappedExpression.operator === "!=") {
37547
+ const isLeftNullish = leftValue.value === null || leftValue.value === void 0;
37548
+ const isRightNullish = rightValue.value === null || rightValue.value === void 0;
37549
+ if (!isLeftNullish && !isRightNullish && typeof leftValue.value !== typeof rightValue.value) return null;
37550
+ const areEqual = isLeftNullish || isRightNullish ? isLeftNullish && isRightNullish : leftValue.value === rightValue.value;
37551
+ return { value: unwrappedExpression.operator === "==" ? areEqual : !areEqual };
37552
+ }
37553
+ }
37554
+ return null;
37555
+ };
37556
+ const readStaticUpdaterReturnValue = (updater, scopes) => {
37557
+ if (!isFunctionLike$1(updater) || updater.async || updater.generator) return null;
37558
+ if (!isNodeOfType(updater.body, "BlockStatement")) return readStaticEffectValue(updater.body, scopes, null, null);
37559
+ if (updater.body.body.length === 0) return { value: void 0 };
37560
+ if (updater.body.body.length !== 1) return null;
37561
+ const returnStatement = updater.body.body[0];
37562
+ if (!isNodeOfType(returnStatement, "ReturnStatement")) return null;
37563
+ if (!returnStatement.argument) return { value: void 0 };
37564
+ return readStaticEffectValue(returnStatement.argument, scopes, null, null);
37565
+ };
37566
+ const readStaticSetterValue = (setterCall, scopes) => {
37567
+ const argument = setterCall.arguments[0];
37568
+ if (!argument) return { value: void 0 };
37569
+ if (isNodeOfType(argument, "SpreadElement")) return null;
37570
+ const updater = resolveExactLocalFunction(argument, scopes);
37571
+ if (updater) return readStaticUpdaterReturnValue(updater, scopes);
37572
+ return readStaticEffectValue(argument, scopes, null, null);
37573
+ };
37574
+ const collectStateWritesInEffect = (analysisFunctions, setterToStateName, scopes) => {
37575
+ const stateWrites = /* @__PURE__ */ new Map();
35887
37576
  visitSynchronousFunctionBodies(analysisFunctions, (child) => {
35888
37577
  if (!isNodeOfType(child, "CallExpression")) return;
35889
37578
  if (!isNodeOfType(child.callee, "Identifier")) return;
35890
37579
  const stateName = setterToStateName.get(child.callee.name);
35891
- if (stateName) writtenStateNames.add(stateName);
37580
+ if (!stateName) return;
37581
+ const writeInfo = stateWrites.get(stateName) ?? {
37582
+ values: /* @__PURE__ */ new Set(),
37583
+ hasUnknownValue: false
37584
+ };
37585
+ const staticValue = readStaticSetterValue(child, scopes);
37586
+ if (staticValue) writeInfo.values.add(staticValue.value);
37587
+ else writeInfo.hasUnknownValue = true;
37588
+ stateWrites.set(stateName, writeInfo);
35892
37589
  });
35893
- return writtenStateNames;
37590
+ return stateWrites;
37591
+ };
37592
+ const isGlobalBooleanCall = (node, scopes) => {
37593
+ return isNodeOfType(node, "CallExpression") && isNodeOfType(node.callee, "Identifier") && node.callee.name === "Boolean" && scopes.isGlobalReference(node.callee);
37594
+ };
37595
+ const isWorkNodeReachableForStateValue = (workNode, stateSymbolId, stateValue, scopes) => {
37596
+ let currentNode = workNode;
37597
+ while (currentNode.parent) {
37598
+ const parentNode = currentNode.parent;
37599
+ if (isFunctionLike$1(parentNode)) break;
37600
+ if (isNodeOfType(parentNode, "IfStatement")) {
37601
+ const testValue = readStaticEffectValue(parentNode.test, scopes, stateSymbolId, stateValue);
37602
+ if (testValue) {
37603
+ if (currentNode === parentNode.consequent && !testValue.value) return false;
37604
+ if (currentNode === parentNode.alternate && testValue.value) return false;
37605
+ }
37606
+ }
37607
+ if (isNodeOfType(parentNode, "ConditionalExpression")) {
37608
+ const testValue = readStaticEffectValue(parentNode.test, scopes, stateSymbolId, stateValue);
37609
+ if (testValue) {
37610
+ if (currentNode === parentNode.consequent && !testValue.value) return false;
37611
+ if (currentNode === parentNode.alternate && testValue.value) return false;
37612
+ }
37613
+ }
37614
+ if (isNodeOfType(parentNode, "LogicalExpression") && currentNode === parentNode.right) {
37615
+ const leftValue = readStaticEffectValue(parentNode.left, scopes, stateSymbolId, stateValue);
37616
+ if (leftValue) {
37617
+ if (parentNode.operator === "&&" && !leftValue.value) return false;
37618
+ if (parentNode.operator === "||" && leftValue.value) return false;
37619
+ if (parentNode.operator === "??" && leftValue.value !== null && leftValue.value !== void 0) return false;
37620
+ }
37621
+ }
37622
+ if (isNodeOfType(parentNode, "BlockStatement")) {
37623
+ const statementIndex = parentNode.body.findIndex((statement) => statement === currentNode);
37624
+ if (statementIndex >= 0) for (let index = 0; index < statementIndex; index += 1) {
37625
+ const earlierStatement = parentNode.body[index];
37626
+ if (!isNodeOfType(earlierStatement, "IfStatement") || earlierStatement.alternate || !statementAlwaysExits(earlierStatement.consequent)) continue;
37627
+ if (readStaticEffectValue(earlierStatement.test, scopes, stateSymbolId, stateValue)?.value) return false;
37628
+ }
37629
+ }
37630
+ currentNode = parentNode;
37631
+ }
37632
+ return true;
37633
+ };
37634
+ const isReaderWorkNode = (node, analysisFunctions, scopes) => {
37635
+ if (isNodeOfType(node, "CallExpression")) {
37636
+ if (isGlobalBooleanCall(node, scopes)) return false;
37637
+ const invokedFunction = resolveExactLocalFunction(node.callee, scopes);
37638
+ return !invokedFunction || !analysisFunctions.has(invokedFunction);
37639
+ }
37640
+ return isNodeOfType(node, "AssignmentExpression") || isNodeOfType(node, "UpdateExpression") || isNodeOfType(node, "NewExpression") || isNodeOfType(node, "TaggedTemplateExpression") || isNodeOfType(node, "ThrowStatement") || isNodeOfType(node, "UnaryExpression") && node.operator === "delete";
37641
+ };
37642
+ const canStateWriteReachReaderWork = (writeInfo, readerEffect, stateSymbolId, scopes) => {
37643
+ if (writeInfo.hasUnknownValue || stateSymbolId === null) return true;
37644
+ for (const writtenValue of writeInfo.values) {
37645
+ const stateValue = { value: writtenValue };
37646
+ let didFindReachableWork = false;
37647
+ visitSynchronousFunctionBodies(readerEffect.analysisFunctions, (child) => {
37648
+ if (didFindReachableWork || !isReaderWorkNode(child, readerEffect.analysisFunctions, scopes)) return;
37649
+ if (isWorkNodeReachableForStateValue(child, stateSymbolId, stateValue, scopes)) didFindReachableWork = true;
37650
+ });
37651
+ if (didFindReachableWork) return true;
37652
+ }
37653
+ return false;
35894
37654
  };
35895
37655
  const EMPTY_CLEANUP_NAME_SET = /* @__PURE__ */ new Set();
37656
+ const NON_CONTAMINATING_MAP_METHOD_NAMES = new Set([
37657
+ "clear",
37658
+ "delete",
37659
+ "entries",
37660
+ "get",
37661
+ "has",
37662
+ "keys",
37663
+ "values"
37664
+ ]);
35896
37665
  const isFunctionShapedReturn = (returnedValue, setterToStateName, isExplicitReturnStatement) => {
35897
37666
  if (isNodeOfType(returnedValue, "ArrowFunctionExpression") || isNodeOfType(returnedValue, "FunctionExpression")) return true;
35898
37667
  if (isNodeOfType(returnedValue, "CallExpression")) {
@@ -35943,27 +37712,135 @@ const callsOpaqueExternalSetter = (analysisFunctions, setterToStateName) => {
35943
37712
  });
35944
37713
  return didFindOpaqueSetterCall;
35945
37714
  };
37715
+ const isReactRefCall = (expression, scopes) => isNodeOfType(expression, "CallExpression") && (isReactApiCall(expression, "useRef", scopes, {
37716
+ allowGlobalReactNamespace: true,
37717
+ allowUnboundBareCalls: true,
37718
+ resolveNamedAliases: true
37719
+ }) || isReactApiCall(expression, "createRef", scopes, {
37720
+ allowGlobalReactNamespace: true,
37721
+ allowUnboundBareCalls: true,
37722
+ resolveNamedAliases: true
37723
+ }));
37724
+ const getDirectReactRefSymbol = (rawExpression, scopes) => {
37725
+ const expression = stripParenExpression(rawExpression);
37726
+ if (!isNodeOfType(expression, "Identifier")) return null;
37727
+ const symbol = scopes.symbolFor(expression);
37728
+ if (!symbol) return null;
37729
+ const initializer = getDirectUnreassignedInitializer(symbol);
37730
+ return initializer && isReactRefCall(stripParenExpression(initializer), scopes) ? symbol : null;
37731
+ };
37732
+ const isReactNativeJsxElement = (openingElement, scopes) => {
37733
+ if (!isNodeOfType(openingElement.name, "JSXIdentifier")) return false;
37734
+ const symbol = scopes.symbolFor(openingElement.name);
37735
+ const importDeclaration = symbol?.declarationNode.parent;
37736
+ return Boolean(symbol?.kind === "import" && importDeclaration && isNodeOfType(importDeclaration, "ImportDeclaration") && importDeclaration.source.value === "react-native");
37737
+ };
37738
+ const isDirectHostJsxRef = (symbol, scopes) => {
37739
+ let hostRefCount = 0;
37740
+ for (const reference of symbol.references) {
37741
+ const expression = findTransparentExpressionRoot(reference.identifier);
37742
+ const container = expression.parent;
37743
+ if (isNodeOfType(container, "MemberExpression") && container.object === expression && getStaticPropertyName(container) === "current") continue;
37744
+ if (!container || !isNodeOfType(container, "JSXExpressionContainer") || container.expression !== expression) return false;
37745
+ const attribute = container.parent;
37746
+ if (!attribute || !isNodeOfType(attribute, "JSXAttribute") || getJsxAttributeName(attribute.name) !== "ref") return false;
37747
+ const openingElement = attribute.parent;
37748
+ if (!openingElement || !isNodeOfType(openingElement, "JSXOpeningElement") || !isProvenIntrinsicJsxElement(openingElement, scopes) && !isReactNativeJsxElement(openingElement, scopes)) return false;
37749
+ hostRefCount += 1;
37750
+ }
37751
+ return hostRefCount > 0;
37752
+ };
37753
+ const isIntrinsicRefCallbackParameter = (expression, scopes) => {
37754
+ const identifier = stripParenExpression(expression);
37755
+ if (!isNodeOfType(identifier, "Identifier")) return false;
37756
+ const callback = findEnclosingFunction$1(identifier);
37757
+ if (!callback || !isFunctionLike$1(callback) || !isInlineIntrinsicRefCallback(callback, scopes)) return false;
37758
+ const rawFirstParameter = callback.params?.[0];
37759
+ const firstParameter = isNodeOfType(rawFirstParameter, "AssignmentPattern") ? rawFirstParameter.left : rawFirstParameter;
37760
+ const symbol = scopes.symbolFor(identifier);
37761
+ return Boolean(firstParameter && symbol?.bindingIdentifier === firstParameter);
37762
+ };
37763
+ const getDirectReactRefCall = (symbol, scopes) => {
37764
+ const initializer = getDirectUnreassignedInitializer(symbol);
37765
+ if (!initializer) return null;
37766
+ const expression = stripParenExpression(initializer);
37767
+ return isNodeOfType(expression, "CallExpression") && isReactRefCall(expression, scopes) ? expression : null;
37768
+ };
37769
+ const storesOnlyIntrinsicRefCallbackValues = (symbol, scopes) => {
37770
+ const initialValue = getDirectReactRefCall(symbol, scopes)?.arguments?.[0];
37771
+ if (!initialValue || !isNodeOfType(initialValue, "NewExpression") || !isNodeOfType(initialValue.callee, "Identifier") || initialValue.callee.name !== "Map" || !scopes.isGlobalReference(initialValue.callee) || initialValue.arguments.length !== 0) return false;
37772
+ let intrinsicValueWriteCount = 0;
37773
+ for (const reference of symbol.references) {
37774
+ const identifier = findTransparentExpressionRoot(reference.identifier);
37775
+ const currentMember = identifier.parent;
37776
+ if (!isNodeOfType(currentMember, "MemberExpression") || currentMember.object !== identifier || getStaticPropertyName(currentMember) !== "current") return false;
37777
+ const currentExpression = findTransparentExpressionRoot(currentMember);
37778
+ const methodMember = currentExpression.parent;
37779
+ if (!isNodeOfType(methodMember, "MemberExpression") || methodMember.object !== currentExpression) return false;
37780
+ const methodName = getStaticPropertyName(methodMember);
37781
+ if (methodName === "size") continue;
37782
+ const call = methodMember.parent;
37783
+ if (!isNodeOfType(call, "CallExpression") || call.callee !== methodMember) return false;
37784
+ if (methodName && NON_CONTAMINATING_MAP_METHOD_NAMES.has(methodName)) continue;
37785
+ if (methodName !== "set") return false;
37786
+ const storedValue = call.arguments[1];
37787
+ if (!storedValue || isNodeOfType(storedValue, "SpreadElement") || !isIntrinsicRefCallbackParameter(storedValue, scopes)) return false;
37788
+ intrinsicValueWriteCount += 1;
37789
+ }
37790
+ return intrinsicValueWriteCount > 0;
37791
+ };
37792
+ const isDerivedFromProvenDomRefCurrent = (rawExpression, scopes, didReadCollectionValue = false, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
37793
+ const expression = stripParenExpression(rawExpression);
37794
+ if (isNodeOfType(expression, "Identifier")) {
37795
+ const symbol = scopes.symbolFor(expression);
37796
+ if (!symbol || visitedSymbolIds.has(symbol.id)) return false;
37797
+ const initializer = getDirectUnreassignedInitializer(symbol);
37798
+ if (!initializer) return false;
37799
+ visitedSymbolIds.add(symbol.id);
37800
+ return isDerivedFromProvenDomRefCurrent(initializer, scopes, didReadCollectionValue, visitedSymbolIds);
37801
+ }
37802
+ if (isNodeOfType(expression, "MemberExpression")) {
37803
+ if (getStaticPropertyName(expression) === "current") {
37804
+ const symbol = getDirectReactRefSymbol(expression.object, scopes);
37805
+ return Boolean(symbol && (isDirectHostJsxRef(symbol, scopes) || didReadCollectionValue && storesOnlyIntrinsicRefCallbackValues(symbol, scopes)));
37806
+ }
37807
+ return isDerivedFromProvenDomRefCurrent(expression.object, scopes, didReadCollectionValue, visitedSymbolIds);
37808
+ }
37809
+ if (!isNodeOfType(expression, "CallExpression")) return false;
37810
+ const callee = stripParenExpression(expression.callee);
37811
+ if (!isNodeOfType(callee, "MemberExpression")) return false;
37812
+ return isDerivedFromProvenDomRefCurrent(callee.object, scopes, didReadCollectionValue || getStaticPropertyName(callee) === "get", visitedSymbolIds);
37813
+ };
37814
+ const isCommittedDomSyncNode = (node, scopes) => {
37815
+ if (!isNodeOfType(node, "CallExpression")) return false;
37816
+ const callee = stripParenExpression(node.callee);
37817
+ if (!isNodeOfType(callee, "MemberExpression")) return false;
37818
+ const propertyName = getStaticPropertyName(callee);
37819
+ if (propertyName === null || !EXTERNAL_SYNC_DOM_MEMBER_METHOD_NAMES.has(propertyName)) return false;
37820
+ return isDerivedFromProvenDomRefCurrent(callee.object, scopes) || isProvenBrowserApiReceiver(callee.object, "dom-event-target", scopes);
37821
+ };
35946
37822
  const isExternalSyncNode = (node) => {
35947
37823
  if (isNodeOfType(node, "NewExpression")) return isNodeOfType(node.callee, "Identifier") && EXTERNAL_SYNC_OBSERVER_CONSTRUCTORS.has(node.callee.name);
35948
37824
  if (isNodeOfType(node, "AssignmentExpression")) return isNodeOfType(node.left, "MemberExpression") && isNodeOfType(node.left.property, "Identifier") && node.left.property.name === "current";
35949
37825
  if (!isNodeOfType(node, "CallExpression")) return false;
35950
37826
  if (isNodeOfType(node.callee, "Identifier")) return EXTERNAL_SYNC_DIRECT_CALLEE_NAMES.has(node.callee.name);
35951
- if (!isNodeOfType(node.callee, "MemberExpression") || !isNodeOfType(node.callee.property, "Identifier")) return false;
35952
- const propertyName = node.callee.property.name;
37827
+ if (!isNodeOfType(node.callee, "MemberExpression")) return false;
37828
+ const propertyName = getStaticPropertyName(node.callee);
37829
+ if (propertyName === null) return false;
35953
37830
  if (EXTERNAL_SYNC_MEMBER_METHOD_NAMES.has(propertyName)) return true;
35954
37831
  if (isBrowserStorageReceiver(node.callee.object)) return true;
35955
37832
  if (!EXTERNAL_SYNC_AMBIGUOUS_HTTP_METHOD_NAMES.has(propertyName)) return false;
35956
37833
  const receiverRootName = getRootIdentifierName(node.callee.object);
35957
37834
  return receiverRootName !== null && EXTERNAL_SYNC_HTTP_CLIENT_RECEIVERS.has(receiverRootName);
35958
37835
  };
35959
- const isExternalSyncEffect = (effectCallback, analysisFunctions, setterToStateName) => {
37836
+ const isExternalSyncEffect = (effectCallback, analysisFunctions, setterToStateName, scopes, allowCommittedDomSync) => {
35960
37837
  if (!isFunctionLike$1(effectCallback)) return false;
35961
37838
  if (!isNodeOfType(effectCallback.body, "BlockStatement")) {
35962
37839
  if (isFunctionShapedReturn(effectCallback.body, setterToStateName, false)) return true;
35963
37840
  } else for (const statement of effectCallback.body.body ?? []) if (isNodeOfType(statement, "ReturnStatement") && statement.argument && isFunctionShapedReturn(statement.argument, setterToStateName, true)) return true;
35964
37841
  let didFindExternalCall = false;
35965
37842
  visitSynchronousFunctionBodies(analysisFunctions, (child) => {
35966
- if (isExternalSyncNode(child)) didFindExternalCall = true;
37843
+ if (isExternalSyncNode(child) || allowCommittedDomSync && isCommittedDomSyncNode(child, scopes)) didFindExternalCall = true;
35967
37844
  });
35968
37845
  return didFindExternalCall;
35969
37846
  };
@@ -35979,32 +37856,45 @@ const noEffectChain = defineRule({
35979
37856
  const useStateBindings = collectUseStateBindings(componentBody);
35980
37857
  if (useStateBindings.length === 0) return;
35981
37858
  const setterToStateName = /* @__PURE__ */ new Map();
35982
- for (const binding of useStateBindings) setterToStateName.set(binding.setterName, binding.valueName);
37859
+ const stateSymbolIds = /* @__PURE__ */ new Map();
37860
+ for (const binding of useStateBindings) {
37861
+ setterToStateName.set(binding.setterName, binding.valueName);
37862
+ if (!isNodeOfType(binding.declarator.id, "ArrayPattern")) continue;
37863
+ const stateIdentifier = binding.declarator.id.elements[0];
37864
+ if (isNodeOfType(stateIdentifier, "Identifier")) {
37865
+ const stateSymbol = context.scopes.symbolFor(stateIdentifier);
37866
+ if (stateSymbol) stateSymbolIds.set(binding.valueName, stateSymbol.id);
37867
+ }
37868
+ }
35983
37869
  const storageSetterNames = collectStorageHookSetterNames(componentBody);
35984
37870
  const effectInfos = [];
35985
37871
  for (const effectCall of findTopLevelEffectCalls(componentBody)) {
35986
37872
  const callback = getEffectCallback(effectCall, context.scopes);
35987
37873
  if (!callback || !isFunctionLike$1(callback) || callback.async) continue;
35988
37874
  const analysisFunctions = collectSynchronouslyInvokedFunctions(callback, context.scopes);
35989
- const writtenStateNames = collectWrittenStateNamesInEffect(analysisFunctions, setterToStateName);
37875
+ const stateWrites = collectStateWritesInEffect(analysisFunctions, setterToStateName, context.scopes);
37876
+ const writtenStateNames = new Set(stateWrites.keys());
35990
37877
  effectInfos.push({
35991
37878
  node: effectCall,
35992
37879
  depNames: collectDepIdentifierNames(effectCall),
35993
- writtenStateNames,
35994
- isExternalSync: isExternalSyncEffect(callback, analysisFunctions, setterToStateName) || callsStorageHookSetter(analysisFunctions, storageSetterNames) || writtenStateNames.size === 0 && callsOpaqueExternalSetter(analysisFunctions, setterToStateName)
37880
+ stateWrites,
37881
+ analysisFunctions,
37882
+ isExternalSync: isExternalSyncEffect(callback, analysisFunctions, setterToStateName, context.scopes, writtenStateNames.size === 0) || callsStorageHookSetter(analysisFunctions, storageSetterNames) || writtenStateNames.size === 0 && callsOpaqueExternalSetter(analysisFunctions, setterToStateName)
35995
37883
  });
35996
37884
  }
35997
37885
  if (effectInfos.length < 2) return;
35998
37886
  const reportedNodes = /* @__PURE__ */ new Set();
35999
37887
  for (const writerEffect of effectInfos) {
36000
37888
  if (writerEffect.isExternalSync) continue;
36001
- if (writerEffect.writtenStateNames.size === 0) continue;
37889
+ if (writerEffect.stateWrites.size === 0) continue;
36002
37890
  for (const readerEffect of effectInfos) {
36003
37891
  if (readerEffect === writerEffect) continue;
36004
37892
  if (readerEffect.isExternalSync) continue;
36005
37893
  if (readerEffect.depNames.size === 0) continue;
36006
37894
  let chainedStateName = null;
36007
- for (const writtenName of writerEffect.writtenStateNames) if (readerEffect.depNames.has(writtenName)) {
37895
+ for (const [writtenName, writeInfo] of writerEffect.stateWrites) {
37896
+ if (!readerEffect.depNames.has(writtenName)) continue;
37897
+ if (!canStateWriteReachReaderWork(writeInfo, readerEffect, stateSymbolIds.get(writtenName) ?? null, context.scopes)) continue;
36008
37898
  chainedStateName = writtenName;
36009
37899
  break;
36010
37900
  }
@@ -39671,6 +41561,18 @@ const functionContainsProvenReactHookCall = (functionNode, scopes) => {
39671
41561
  return containsReactHookCall;
39672
41562
  };
39673
41563
  //#endregion
41564
+ //#region src/plugin/utils/function-returns-only-null.ts
41565
+ const isNullExpression = (expression) => {
41566
+ const candidate = stripParenExpression(expression);
41567
+ return isNodeOfType(candidate, "Literal") && candidate.value === null;
41568
+ };
41569
+ const functionReturnsOnlyNull = (functionNode) => {
41570
+ if (!isFunctionLike$1(functionNode)) return false;
41571
+ if (!isNodeOfType(functionNode.body, "BlockStatement")) return isNullExpression(functionNode.body);
41572
+ const returnStatements = collectFunctionReturnStatements(functionNode);
41573
+ return returnStatements.length > 0 && returnStatements.every((returnStatement) => Boolean(returnStatement.argument && isNullExpression(returnStatement.argument)));
41574
+ };
41575
+ //#endregion
39674
41576
  //#region src/plugin/utils/function-returns-props-children.ts
39675
41577
  const functionReturnsPropsChildren = (functionNode, scopes, controlFlow) => {
39676
41578
  if (!isFunctionLike$1(functionNode) || functionNode.params.length === 0) return false;
@@ -39703,17 +41605,8 @@ const functionReturnsPropsChildren = (functionNode, scopes, controlFlow) => {
39703
41605
  }, controlFlow);
39704
41606
  };
39705
41607
  //#endregion
39706
- //#region src/plugin/utils/function-returns-only-null.ts
39707
- const isNullExpression = (expression) => {
39708
- const candidate = stripParenExpression(expression);
39709
- return isNodeOfType(candidate, "Literal") && candidate.value === null;
39710
- };
39711
- const functionReturnsOnlyNull = (functionNode) => {
39712
- if (!isFunctionLike$1(functionNode)) return false;
39713
- if (!isNodeOfType(functionNode.body, "BlockStatement")) return isNullExpression(functionNode.body);
39714
- const returnStatements = collectFunctionReturnStatements(functionNode);
39715
- return returnStatements.length > 0 && returnStatements.every((returnStatement) => Boolean(returnStatement.argument && isNullExpression(returnStatement.argument)));
39716
- };
41608
+ //#region src/plugin/utils/function-has-react-component-evidence.ts
41609
+ const functionHasReactComponentEvidence = (functionNode, scopes, controlFlow) => functionContainsReactRenderOutput(functionNode, scopes, controlFlow) || functionReturnsPropsChildren(functionNode, scopes, controlFlow) || functionContainsProvenReactHookCall(functionNode, scopes) && functionReturnsOnlyNull(functionNode);
39717
41610
  //#endregion
39718
41611
  //#region src/plugin/utils/is-proven-styled-component-expression.ts
39719
41612
  const findFactoryRoot = (node) => {
@@ -39746,17 +41639,16 @@ const isProvenStyledComponentExpression = (expression, scopes) => {
39746
41639
  //#region src/plugin/utils/is-proven-react-component-symbol.ts
39747
41640
  const REACT_COMPONENT_HOC_NAMES = new Set(["memo", "forwardRef"]);
39748
41641
  const LEGACY_REACT_COMPONENT_FACTORY_NAMES = new Set(["createClass", "createReactClass"]);
39749
- const functionHasComponentEvidence = (functionNode, scopes, controlFlow) => functionContainsReactRenderOutput(functionNode, scopes, controlFlow) || functionReturnsPropsChildren(functionNode, scopes, controlFlow) || functionContainsProvenReactHookCall(functionNode, scopes) && functionReturnsOnlyNull(functionNode);
39750
41642
  const isProvenReactComponentExpression = (expression, scopes, controlFlow, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
39751
41643
  const candidate = stripParenExpression(expression);
39752
- if (isInlineFunctionExpression(candidate)) return functionHasComponentEvidence(candidate, scopes, controlFlow);
41644
+ if (isInlineFunctionExpression(candidate)) return functionHasReactComponentEvidence(candidate, scopes, controlFlow);
39753
41645
  if (isNodeOfType(candidate, "ClassExpression")) return isProvenReactClassComponent(candidate, scopes);
39754
41646
  if (isProvenStyledComponentExpression(candidate, scopes)) return true;
39755
41647
  if (isNodeOfType(candidate, "Identifier")) {
39756
41648
  const symbol = scopes.symbolFor(candidate);
39757
41649
  if (!symbol || visitedSymbolIds.has(symbol.id) || hasSymbolWriteBefore(symbol, candidate, scopes)) return false;
39758
41650
  visitedSymbolIds.add(symbol.id);
39759
- if (isNodeOfType(symbol.declarationNode, "FunctionDeclaration")) return functionHasComponentEvidence(symbol.declarationNode, scopes, controlFlow);
41651
+ if (isNodeOfType(symbol.declarationNode, "FunctionDeclaration")) return functionHasReactComponentEvidence(symbol.declarationNode, scopes, controlFlow);
39760
41652
  if (isNodeOfType(symbol.declarationNode, "ClassDeclaration") || isNodeOfType(symbol.declarationNode, "ClassExpression")) return isProvenReactClassComponent(symbol.declarationNode, scopes);
39761
41653
  return Boolean(symbol.initializer && isProvenReactComponentExpression(symbol.initializer, scopes, controlFlow, visitedSymbolIds));
39762
41654
  }
@@ -39783,7 +41675,7 @@ const isProvenReactComponentSymbol = (symbol, scopes, controlFlow, componentRefe
39783
41675
  for (const candidateSymbol of candidateSymbols) {
39784
41676
  if (hasSymbolWriteBefore(candidateSymbol, componentReference, scopes)) continue;
39785
41677
  if (isComponentDeclaration(candidateSymbol.declarationNode)) {
39786
- if (functionHasComponentEvidence(candidateSymbol.declarationNode, scopes, controlFlow)) return true;
41678
+ if (functionHasReactComponentEvidence(candidateSymbol.declarationNode, scopes, controlFlow)) return true;
39787
41679
  continue;
39788
41680
  }
39789
41681
  const initializer = candidateSymbol.initializer ? stripParenExpression(candidateSymbol.initializer) : null;
@@ -41285,11 +43177,6 @@ const SAME_REFERENCE_ARRAY_RETURN_METHODS = new Set([
41285
43177
  "reverse",
41286
43178
  "sort"
41287
43179
  ]);
41288
- const OBJECT_MUTATION_METHODS = new Set([
41289
- "assign",
41290
- "defineProperties",
41291
- "defineProperty"
41292
- ]);
41293
43180
  const REFLECT_MUTATION_METHODS = new Set(["deleteProperty", "set"]);
41294
43181
  const cloneReducerPathState = (state) => ({
41295
43182
  originalStateReferenceNames: new Set(state.originalStateReferenceNames),
@@ -41385,7 +43272,7 @@ const collectReducerStateMutationsInExpressionOrStatement = (node, state) => {
41385
43272
  }
41386
43273
  if (!isNodeOfType(unwrappedChild, "CallExpression")) return;
41387
43274
  const firstArgument = unwrappedChild.arguments?.[0];
41388
- if (firstArgument && isExpressionRootedInMutableReducerStateSource(firstArgument, state) && (isStaticMethodCallOnNamedObject(unwrappedChild, "Object", OBJECT_MUTATION_METHODS) || isStaticMethodCallOnNamedObject(unwrappedChild, "Reflect", REFLECT_MUTATION_METHODS))) {
43275
+ if (firstArgument && isExpressionRootedInMutableReducerStateSource(firstArgument, state) && (isStaticMethodCallOnNamedObject(unwrappedChild, "Object", OBJECT_PROPERTY_MUTATION_METHOD_NAMES) || isStaticMethodCallOnNamedObject(unwrappedChild, "Reflect", REFLECT_MUTATION_METHODS))) {
41389
43276
  mutations.push({ node: unwrappedChild });
41390
43277
  return;
41391
43278
  }
@@ -43569,6 +45456,53 @@ const noPropCallbackInEffect = defineRule({
43569
45456
  });
43570
45457
  //#endregion
43571
45458
  //#region src/plugin/rules/state-and-effects/no-prop-callback-in-render.ts
45459
+ const functionBindingSymbols = (functionNode, scopes) => {
45460
+ let bindingIdentifier = null;
45461
+ if (isNodeOfType(functionNode, "FunctionDeclaration") && functionNode.id) bindingIdentifier = functionNode.id;
45462
+ else {
45463
+ let bindingExpression = findTransparentExpressionRoot(functionNode);
45464
+ let parent = bindingExpression.parent;
45465
+ while (isNodeOfType(parent, "CallExpression") && parent.arguments[0] === bindingExpression) {
45466
+ const callee = parent.callee;
45467
+ const wrapperName = isNodeOfType(callee, "Identifier") ? callee.name : isNodeOfType(callee, "MemberExpression") && isNodeOfType(callee.property, "Identifier") ? callee.property.name : null;
45468
+ if (!isReactApiCall(parent, REACT_HOC_NAMES, scopes, {
45469
+ allowGlobalReactNamespace: true,
45470
+ resolveNamedAliases: true
45471
+ }) && (!wrapperName || REACT_HOC_NAMES.has(wrapperName) || !COMPONENT_HOC_WRAPPER_NAMES.has(wrapperName))) break;
45472
+ bindingExpression = findTransparentExpressionRoot(parent);
45473
+ parent = bindingExpression.parent;
45474
+ }
45475
+ if (isNodeOfType(parent, "VariableDeclarator") && parent.init === bindingExpression && isNodeOfType(parent.id, "Identifier")) bindingIdentifier = parent.id;
45476
+ }
45477
+ if (!bindingIdentifier) return [];
45478
+ let scope = scopes.scopeFor(functionNode);
45479
+ while (scope) {
45480
+ const symbols = scope.symbols.filter((symbol) => symbol.bindingIdentifier === bindingIdentifier);
45481
+ if (symbols.length > 0) return symbols;
45482
+ scope = scope.parent;
45483
+ }
45484
+ return [];
45485
+ };
45486
+ const symbolHasReactComponentUse = (symbol, scopes, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
45487
+ if (visitedSymbolIds.has(symbol.id)) return false;
45488
+ visitedSymbolIds.add(symbol.id);
45489
+ for (const reference of symbol.references) {
45490
+ const identifier = reference.identifier;
45491
+ if (hasSymbolWriteBefore(symbol, identifier, scopes)) continue;
45492
+ const parent = identifier.parent;
45493
+ if (isNodeOfType(parent, "JSXOpeningElement") && isNodeOfType(parent.name, "JSXIdentifier") && parent.name === identifier) return true;
45494
+ const expression = findTransparentExpressionRoot(identifier);
45495
+ const expressionParent = expression.parent;
45496
+ if (isNodeOfType(expressionParent, "CallExpression") && expressionParent.arguments[0] === expression && isReactApiCall(expressionParent, "createElement", scopes, { resolveNamedAliases: true })) return true;
45497
+ if (!isNodeOfType(expressionParent, "VariableDeclarator") || expressionParent.init !== expression || !isNodeOfType(expressionParent.id, "Identifier") || !isNodeOfType(expressionParent.parent, "VariableDeclaration") || expressionParent.parent.kind !== "const") continue;
45498
+ const aliasSymbol = scopes.symbolFor(expressionParent.id);
45499
+ if (aliasSymbol && symbolHasReactComponentUse(aliasSymbol, scopes, visitedSymbolIds)) return true;
45500
+ }
45501
+ return false;
45502
+ };
45503
+ const functionHasReactComponentUse = (functionNode, scopes) => {
45504
+ return functionBindingSymbols(functionNode, scopes).some((symbol) => symbolHasReactComponentUse(symbol, scopes));
45505
+ };
43572
45506
  const isPreservedThroughConciseArrow = (callExpression, scopes) => {
43573
45507
  let node = callExpression;
43574
45508
  let parent = node.parent;
@@ -43615,7 +45549,10 @@ const noPropCallbackInRender = defineRule({
43615
45549
  create: (context) => ({ CallExpression(node) {
43616
45550
  if (!isResultDiscardedCall(node)) return;
43617
45551
  if (isPreservedThroughConciseArrow(node, context.scopes)) return;
43618
- if (!findRenderPhaseComponentOrHook(node, context.scopes)) return;
45552
+ const renderPhaseOwner = findRenderPhaseComponentOrHook(node, context.scopes);
45553
+ if (!renderPhaseOwner) return;
45554
+ const renderPhaseOwnerName = componentOrHookDisplayNameForFunction(renderPhaseOwner);
45555
+ if (!renderPhaseOwnerName || !isReactHookName(renderPhaseOwnerName) && !functionHasReactComponentEvidence(renderPhaseOwner, context.scopes, context.cfg) && !functionHasReactComponentUse(renderPhaseOwner, context.scopes)) return;
43619
45556
  const analysis = getProgramAnalysis(node);
43620
45557
  if (!analysis) return;
43621
45558
  const callee = stripParenExpression(node.callee);
@@ -44272,6 +46209,7 @@ const noRedundantRoles = defineRule({
44272
46209
  create: (context) => {
44273
46210
  const settings = resolveSettings$13(context.settings);
44274
46211
  return { JSXOpeningElement(node) {
46212
+ if (isLocalTestScaffoldJsx(node, context)) return;
44275
46213
  const roleAttr = hasJsxPropIgnoreCase(node.attributes, "role");
44276
46214
  if (!roleAttr) return;
44277
46215
  if (hasJsxPropIgnoreCase(node.attributes, "data-rac")) return;
@@ -44351,11 +46289,110 @@ const isSameRefCurrentMember = (node, refSymbol, scopes) => {
44351
46289
  return isNodeOfType(receiver, "Identifier") && resolveConstIdentifierAlias(receiver, scopes)?.id === refSymbol.id;
44352
46290
  };
44353
46291
  const isSameRefCurrentAlias = (node, refSymbol, scopes) => {
44354
- if (isSameRefCurrentMember(node, refSymbol, scopes)) return true;
44355
- if (!isNodeOfType(node, "Identifier")) return false;
44356
- const aliasSymbol = scopes.symbolFor(node);
46292
+ const expression = stripParenExpression(node);
46293
+ if (isSameRefCurrentMember(expression, refSymbol, scopes)) return true;
46294
+ if (!isNodeOfType(expression, "Identifier")) return false;
46295
+ const aliasSymbol = scopes.symbolFor(expression);
44357
46296
  return aliasSymbol?.kind === "const" && aliasSymbol.initializer !== null && isSameRefCurrentMember(stripParenExpression(aliasSymbol.initializer), refSymbol, scopes);
44358
46297
  };
46298
+ const resolveImmutableInitializationValue = (node, scopes, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
46299
+ const expression = stripParenExpression(node);
46300
+ if (!isNodeOfType(expression, "Identifier")) return expression;
46301
+ const symbol = scopes.symbolFor(expression);
46302
+ if (!symbol || symbol.kind !== "const" || !symbol.initializer || symbol.references.some((reference) => reference.flag !== "read") || visitedSymbolIds.has(symbol.id)) return null;
46303
+ visitedSymbolIds.add(symbol.id);
46304
+ return resolveImmutableInitializationValue(symbol.initializer, scopes, visitedSymbolIds);
46305
+ };
46306
+ const isProvablyTruthyInitializationValue = (node, scopes) => {
46307
+ const expression = resolveImmutableInitializationValue(node, scopes);
46308
+ return Boolean(expression && (isNodeOfType(expression, "NewExpression") || isNodeOfType(expression, "ObjectExpression") || isNodeOfType(expression, "ArrayExpression") || isNodeOfType(expression, "ArrowFunctionExpression") || isNodeOfType(expression, "FunctionExpression") || isNodeOfType(expression, "ClassExpression")));
46309
+ };
46310
+ const getInitializationConstructorName = (node, scopes) => {
46311
+ const expression = resolveImmutableInitializationValue(node, scopes);
46312
+ if (!expression) return null;
46313
+ if (isNodeOfType(expression, "NewExpression")) {
46314
+ const callee = stripParenExpression(expression.callee);
46315
+ return isNodeOfType(callee, "Identifier") ? callee.name : null;
46316
+ }
46317
+ return null;
46318
+ };
46319
+ const isClosedTruthyTypeDomain = (typeNode, initializationValue, scopes) => {
46320
+ const initializationExpression = stripParenExpression(initializationValue);
46321
+ if (isNodeOfType(typeNode, "TSTypeLiteral")) return isNodeOfType(initializationExpression, "ObjectExpression");
46322
+ if (isNodeOfType(typeNode, "TSArrayType") || isNodeOfType(typeNode, "TSTupleType")) return isNodeOfType(initializationExpression, "ArrayExpression");
46323
+ if (isNodeOfType(typeNode, "TSFunctionType") || isNodeOfType(typeNode, "TSConstructorType")) return isNodeOfType(initializationExpression, "ArrowFunctionExpression") || isNodeOfType(initializationExpression, "FunctionExpression") || isNodeOfType(initializationExpression, "ClassExpression");
46324
+ if (isNodeOfType(typeNode, "TSObjectKeyword")) return true;
46325
+ if (!isNodeOfType(typeNode, "TSTypeReference")) return false;
46326
+ const typeName = typeNode.typeName;
46327
+ return isNodeOfType(typeName, "Identifier") && typeName.name === getInitializationConstructorName(initializationExpression, scopes);
46328
+ };
46329
+ const refHasClosedFalsySentinelDomain = (refSymbol, initializationValue, scopes) => {
46330
+ const initializer = refSymbol.initializer ? stripParenExpression(refSymbol.initializer) : null;
46331
+ if (!initializer || !isNodeOfType(initializer, "CallExpression")) return false;
46332
+ const [initialValue] = initializer.arguments ?? [];
46333
+ if (!initialValue || isNodeOfType(initialValue, "SpreadElement") || !isEmptySentinel(initialValue, scopes)) return false;
46334
+ const [declaredType] = initializer.typeArguments?.params ?? [];
46335
+ if (!declaredType || !isNodeOfType(declaredType, "TSUnionType")) return false;
46336
+ let hasEmptySentinel = false;
46337
+ let hasTruthyDomain = false;
46338
+ for (const memberType of declaredType.types ?? []) {
46339
+ if (isNodeOfType(memberType, "TSNullKeyword") || isNodeOfType(memberType, "TSUndefinedKeyword")) {
46340
+ hasEmptySentinel = true;
46341
+ continue;
46342
+ }
46343
+ if (!isClosedTruthyTypeDomain(memberType, initializationValue, scopes)) return false;
46344
+ hasTruthyDomain = true;
46345
+ }
46346
+ return hasEmptySentinel && hasTruthyDomain;
46347
+ };
46348
+ const isSafeRefIdentifierUse = (identifier) => {
46349
+ const expressionRoot = findTransparentExpressionRoot(identifier);
46350
+ const parent = expressionRoot.parent;
46351
+ if (parent && isNodeOfType(parent, "VariableDeclarator") && parent.id === expressionRoot && parent.parent !== null && isNodeOfType(parent.parent, "VariableDeclaration") && parent.parent.kind === "const") return true;
46352
+ if (parent && isNodeOfType(parent, "MemberExpression") && parent.object === expressionRoot && getStaticPropertyName(parent) === "current") return true;
46353
+ if (!parent || !isNodeOfType(parent, "VariableDeclarator") || parent.init !== expressionRoot) return false;
46354
+ return isNodeOfType(parent.id, "Identifier") && parent.parent !== null && isNodeOfType(parent.parent, "VariableDeclaration") && parent.parent.kind === "const";
46355
+ };
46356
+ const refDoesNotEscape = (branchRoot, refSymbol, scopes) => {
46357
+ let didEscape = false;
46358
+ walkAst(branchRoot, (child) => {
46359
+ if (didEscape) return false;
46360
+ if (!isNodeOfType(child, "Identifier")) return;
46361
+ if (resolveConstIdentifierAlias(child, scopes)?.id !== refSymbol.id) return;
46362
+ if (child === refSymbol.bindingIdentifier || isSafeRefIdentifierUse(child)) return;
46363
+ didEscape = true;
46364
+ return false;
46365
+ });
46366
+ return !didEscape;
46367
+ };
46368
+ const expressionContainsRefCurrent = (expression, refSymbol, scopes) => {
46369
+ let didFindRefCurrent = false;
46370
+ walkAst(expression, (child) => {
46371
+ if (didFindRefCurrent) return false;
46372
+ if (resolveReactRefSymbol(child, scopes)?.id !== refSymbol.id) return;
46373
+ didFindRefCurrent = true;
46374
+ return false;
46375
+ });
46376
+ return didFindRefCurrent;
46377
+ };
46378
+ const hasNoCompetingRefCurrentWrite = (branchRoot, assignmentExpression, refSymbol, scopes) => {
46379
+ let writeCount = 0;
46380
+ walkAst(branchRoot, (child) => {
46381
+ if (writeCount > 1) return false;
46382
+ if (isNodeOfType(child, "AssignmentExpression")) {
46383
+ if (expressionContainsRefCurrent(child.left, refSymbol, scopes)) writeCount++;
46384
+ return;
46385
+ }
46386
+ if (isNodeOfType(child, "UpdateExpression") || isNodeOfType(child, "UnaryExpression") && child.operator === "delete") {
46387
+ if (expressionContainsRefCurrent(child.argument, refSymbol, scopes)) writeCount++;
46388
+ return;
46389
+ }
46390
+ if (isNodeOfType(child, "ForInStatement") || isNodeOfType(child, "ForOfStatement")) {
46391
+ if (expressionContainsRefCurrent(child.left, refSymbol, scopes)) writeCount++;
46392
+ }
46393
+ });
46394
+ return writeCount === 1 && expressionContainsRefCurrent(assignmentExpression.left, refSymbol, scopes);
46395
+ };
44359
46396
  const isEmptySentinel = (node, scopes) => isNodeOfType(node, "Literal") && node.value === null || isNodeOfType(node, "Identifier") && node.name === "undefined" && scopes.isGlobalReference(node);
44360
46397
  const hasRepeatedExecutionAncestor = (node, stop) => {
44361
46398
  let ancestor = node.parent;
@@ -44404,18 +46441,22 @@ const hasNoPriorCoExecutableWrite = (assignmentExpression, branchRoot, refSymbol
44404
46441
  const isDocumentedLazyInitialization = (assignmentExpression, refSymbol, scopes) => {
44405
46442
  if (assignmentExpression.operator === "??=" || assignmentExpression.operator === "||=") return true;
44406
46443
  if (assignmentExpression.operator !== "=") return false;
46444
+ const renderOwner = findRenderPhaseComponentOrHook(assignmentExpression, scopes);
46445
+ if (!renderOwner) return false;
44407
46446
  let descendant = assignmentExpression;
44408
46447
  let ancestor = descendant.parent;
44409
46448
  while (ancestor) {
44410
- if (isNodeOfType(ancestor, "IfStatement") && isNodeOfType(ancestor.test, "BinaryExpression") && [
46449
+ const test = isNodeOfType(ancestor, "IfStatement") ? stripParenExpression(ancestor.test) : null;
46450
+ if (isNodeOfType(ancestor, "IfStatement") && test && isNodeOfType(test, "UnaryExpression") && test.operator === "!" && isSameRefCurrentAlias(test.argument, refSymbol, scopes) && ancestor.consequent === descendant && isProvablyTruthyInitializationValue(assignmentExpression.right, scopes) && refHasClosedFalsySentinelDomain(refSymbol, assignmentExpression.right, scopes) && !hasRepeatedExecutionAncestor(assignmentExpression, ancestor.consequent) && !hasRepeatedExecutionAncestor(ancestor, renderOwner) && hasNoPriorCoExecutableWrite(assignmentExpression, ancestor.consequent, refSymbol, scopes) && hasNoCompetingRefCurrentWrite(renderOwner, assignmentExpression, refSymbol, scopes) && refDoesNotEscape(renderOwner, refSymbol, scopes)) return true;
46451
+ if (isNodeOfType(ancestor, "IfStatement") && isNodeOfType(test, "BinaryExpression") && [
44411
46452
  "===",
44412
46453
  "==",
44413
46454
  "!==",
44414
46455
  "!="
44415
- ].includes(ancestor.test.operator)) {
44416
- const { left, right } = ancestor.test;
46456
+ ].includes(test.operator)) {
46457
+ const { left, right } = test;
44417
46458
  const comparesEmptySentinel = isSameRefCurrentAlias(left, refSymbol, scopes) && isEmptySentinel(right, scopes) || isSameRefCurrentAlias(right, refSymbol, scopes) && isEmptySentinel(left, scopes);
44418
- const guardedBranch = ancestor.test.operator === "===" || ancestor.test.operator === "==" ? ancestor.consequent : ancestor.alternate;
46459
+ const guardedBranch = test.operator === "===" || test.operator === "==" ? ancestor.consequent : ancestor.alternate;
44419
46460
  if (comparesEmptySentinel && guardedBranch === descendant && guardedBranch && !hasRepeatedExecutionAncestor(assignmentExpression, guardedBranch) && hasNoPriorCoExecutableWrite(assignmentExpression, guardedBranch, refSymbol, scopes)) return true;
44420
46461
  }
44421
46462
  descendant = ancestor;
@@ -44829,7 +46870,7 @@ const doConditionsImplyFormula = (conditions, target) => {
44829
46870
  }
44830
46871
  return facts.didConflict || evaluateBooleanFormula$1(target, facts.assignments) === true;
44831
46872
  };
44832
- const getFunctionBindingSymbol = (functionNode, scopes) => {
46873
+ const getFunctionBindingSymbol$1 = (functionNode, scopes) => {
44833
46874
  if (isNodeOfType(functionNode, "FunctionDeclaration") && functionNode.id) return scopes.symbolFor(functionNode.id);
44834
46875
  const parent = functionNode.parent;
44835
46876
  if ((isNodeOfType(functionNode, "ArrowFunctionExpression") || isNodeOfType(functionNode, "FunctionExpression")) && isNodeOfType(parent, "VariableDeclarator") && parent.init === functionNode && isNodeOfType(parent.id, "Identifier")) return scopes.symbolFor(parent.id);
@@ -44862,7 +46903,7 @@ const isNodeEvaluatedDuringRender = (node, componentNode, scopes, visitedFunctio
44862
46903
  const synchronousCallbackCall = getSynchronousCallbackCall(functionNode);
44863
46904
  if (synchronousCallbackCall) return isNodeEvaluatedDuringRender(synchronousCallbackCall, componentNode, scopes, visitedFunctionSymbolIds);
44864
46905
  if (executesDuringRender(functionNode, scopes)) return isNodeEvaluatedDuringRender(functionNode.parent ?? functionNode, componentNode, scopes, visitedFunctionSymbolIds);
44865
- const functionSymbol = getFunctionBindingSymbol(functionNode, scopes);
46906
+ const functionSymbol = getFunctionBindingSymbol$1(functionNode, scopes);
44866
46907
  if (!functionSymbol || visitedFunctionSymbolIds.has(functionSymbol.id)) return false;
44867
46908
  visitedFunctionSymbolIds.add(functionSymbol.id);
44868
46909
  let callCount = 0;
@@ -44911,7 +46952,7 @@ const collectExposureConditions = (analysis, context, node, componentNode, prote
44911
46952
  parent = synchronousCallbackCall.parent;
44912
46953
  continue;
44913
46954
  }
44914
- const functionSymbol = getFunctionBindingSymbol(parent, context.scopes);
46955
+ const functionSymbol = getFunctionBindingSymbol$1(parent, context.scopes);
44915
46956
  if (functionSymbol?.references.length === 1) {
44916
46957
  const callExpression = isReferenceDirectlyCalled(functionSymbol.references[0].identifier);
44917
46958
  if (callExpression) {
@@ -45121,7 +47162,7 @@ const getSetterExposureConditions = (analysis, context, setterReference, compone
45121
47162
  const functionNode = findEnclosingFunction$1(setterReference.identifier);
45122
47163
  if (!functionNode) return null;
45123
47164
  if (isInlineJsxCallback(functionNode)) return [collectExposureConditions(analysis, context, functionNode, componentNode, protectedSymbolIds)];
45124
- const functionSymbol = getFunctionBindingSymbol(functionNode, context.scopes);
47165
+ const functionSymbol = getFunctionBindingSymbol$1(functionNode, context.scopes);
45125
47166
  if (!functionSymbol || functionSymbol.references.length === 0) return null;
45126
47167
  const conditionsByReference = [];
45127
47168
  for (const reference of functionSymbol.references) {
@@ -45218,6 +47259,87 @@ const noResetAllStateOnPropChange = defineRule({
45218
47259
  } })
45219
47260
  });
45220
47261
  //#endregion
47262
+ //#region src/plugin/utils/is-proven-framer-motion-jsx-element.ts
47263
+ const MOTION_FACTORY_MODULES = new Set(["framer-motion", "motion/react"]);
47264
+ const MOTION_TAG_NAMESPACE_MODULES = new Set([
47265
+ "framer-motion/client",
47266
+ "framer-motion/m",
47267
+ "motion/react-client",
47268
+ "motion/react-m"
47269
+ ]);
47270
+ const MOTION_FACTORY_EXPORTS = new Set(["m", "motion"]);
47271
+ const getValueImportSource = (symbol) => {
47272
+ if (symbol.kind !== "import") return null;
47273
+ const declaration = symbol.declarationNode.parent;
47274
+ if (!declaration || !isNodeOfType(declaration, "ImportDeclaration") || isTypeOnlyImport(declaration) || isNodeOfType(symbol.declarationNode, "ImportSpecifier") && symbol.declarationNode.importKind === "type") return null;
47275
+ return typeof declaration.source.value === "string" ? declaration.source.value : null;
47276
+ };
47277
+ const getMemberParts = (node) => {
47278
+ if (isNodeOfType(node, "MemberExpression")) {
47279
+ const propertyName = getStaticPropertyName(node);
47280
+ return propertyName ? [node.object, propertyName] : null;
47281
+ }
47282
+ if (isNodeOfType(node, "JSXMemberExpression")) return isNodeOfType(node.property, "JSXIdentifier") ? [node.object, node.property.name] : null;
47283
+ return null;
47284
+ };
47285
+ const resolveSymbol = (node, scopes) => {
47286
+ if (!isNodeOfType(node, "Identifier") && !isNodeOfType(node, "JSXIdentifier")) return null;
47287
+ return resolveConstIdentifierAlias(node, scopes);
47288
+ };
47289
+ const isNamespaceFrom = (node, sources, scopes) => {
47290
+ const symbol = resolveSymbol(stripParenExpression(node), scopes);
47291
+ const source = symbol ? getValueImportSource(symbol) : null;
47292
+ return Boolean(source && sources.has(source) && symbol && isNodeOfType(symbol.declarationNode, "ImportNamespaceSpecifier"));
47293
+ };
47294
+ const isMotionFactory = (rawNode, scopes, visitedSymbolIds) => {
47295
+ const node = stripParenExpression(rawNode);
47296
+ if (isNamespaceFrom(node, MOTION_TAG_NAMESPACE_MODULES, scopes)) return true;
47297
+ const symbol = resolveSymbol(node, scopes);
47298
+ if (symbol?.kind === "import") {
47299
+ const source = getValueImportSource(symbol);
47300
+ const importedName = getImportedName(symbol.declarationNode);
47301
+ return Boolean(source && MOTION_FACTORY_MODULES.has(source) && importedName && MOTION_FACTORY_EXPORTS.has(importedName));
47302
+ }
47303
+ if (symbol?.kind === "const" && symbol.initializer) {
47304
+ if (visitedSymbolIds.has(symbol.id)) return false;
47305
+ visitedSymbolIds.add(symbol.id);
47306
+ return isMotionFactory(symbol.initializer, scopes, visitedSymbolIds);
47307
+ }
47308
+ const memberParts = getMemberParts(node);
47309
+ return Boolean(memberParts && MOTION_FACTORY_EXPORTS.has(memberParts[1]) && isNamespaceFrom(memberParts[0], MOTION_FACTORY_MODULES, scopes));
47310
+ };
47311
+ const isMotionComponent = (rawNode, scopes) => {
47312
+ return isMotionComponentWithVisitedSymbols(rawNode, scopes, /* @__PURE__ */ new Set());
47313
+ };
47314
+ const isMotionComponentWithVisitedSymbols = (rawNode, scopes, visitedSymbolIds) => {
47315
+ const node = stripParenExpression(rawNode);
47316
+ const symbol = resolveSymbol(node, scopes);
47317
+ if (symbol?.kind === "const" && symbol.initializer) {
47318
+ if (visitedSymbolIds.has(symbol.id)) return false;
47319
+ visitedSymbolIds.add(symbol.id);
47320
+ return isMotionComponentWithVisitedSymbols(symbol.initializer, scopes, visitedSymbolIds);
47321
+ }
47322
+ if (symbol?.kind === "import") {
47323
+ const source = getValueImportSource(symbol);
47324
+ return Boolean(source && MOTION_TAG_NAMESPACE_MODULES.has(source) && isNodeOfType(symbol.declarationNode, "ImportSpecifier") && getImportedName(symbol.declarationNode) !== "create");
47325
+ }
47326
+ const memberParts = getMemberParts(node);
47327
+ if (memberParts && isMotionFactory(memberParts[0], scopes, visitedSymbolIds)) return true;
47328
+ if (!isNodeOfType(node, "CallExpression")) return false;
47329
+ if (isMotionFactory(node.callee, scopes, visitedSymbolIds)) return true;
47330
+ const calleeMemberParts = getMemberParts(stripParenExpression(node.callee));
47331
+ return Boolean(calleeMemberParts && calleeMemberParts[1] === "create" && isMotionFactory(calleeMemberParts[0], scopes, visitedSymbolIds));
47332
+ };
47333
+ const isProvenFramerMotionJsxElement = (openingElement, scopes) => {
47334
+ const elementName = openingElement.name;
47335
+ if (isNodeOfType(elementName, "JSXIdentifier")) {
47336
+ if (/^[a-z]/.test(elementName.name)) return false;
47337
+ return isMotionComponent(elementName, scopes);
47338
+ }
47339
+ const memberParts = getMemberParts(elementName);
47340
+ return Boolean(memberParts && isMotionFactory(memberParts[0], scopes, /* @__PURE__ */ new Set()));
47341
+ };
47342
+ //#endregion
45221
47343
  //#region src/plugin/rules/performance/no-scale-from-zero.ts
45222
47344
  const noScaleFromZero = defineRule({
45223
47345
  id: "no-scale-from-zero",
@@ -45228,6 +47350,8 @@ const noScaleFromZero = defineRule({
45228
47350
  create: (context) => ({ JSXAttribute(node) {
45229
47351
  if (!isNodeOfType(node.name, "JSXIdentifier")) return;
45230
47352
  if (node.name.name !== "initial" && node.name.name !== "exit") return;
47353
+ const openingElement = node.parent;
47354
+ if (!openingElement || !isNodeOfType(openingElement, "JSXOpeningElement") || !Object.is(getAuthoritativeJsxAttribute(openingElement.attributes, node.name.name), node) || !isProvenFramerMotionJsxElement(openingElement, context.scopes)) return;
45231
47355
  if (!isNodeOfType(node.value, "JSXExpressionContainer")) return;
45232
47356
  const expression = node.value.expression;
45233
47357
  if (!isNodeOfType(expression, "ObjectExpression")) return;
@@ -45426,7 +47550,7 @@ const isSelfReferentialSentinelValue = (variableName, literalValue) => literalVa
45426
47550
  const isIdentifierLikeKeyNameValue = (literalValue) => {
45427
47551
  const wordSegments = literalValue.replace(/^[_$\s]+|[_$\s]+$/g, "").split(/[_\-:./$]+/).filter((segment) => segment.length > 0);
45428
47552
  if (wordSegments.length < 2) return false;
45429
- return wordSegments.every((segment) => /^[a-z]+$/.test(segment));
47553
+ return wordSegments.every((segment) => /^[a-z]+(?:[A-Z][a-z]+)*$/.test(segment));
45430
47554
  };
45431
47555
  const FRAMEWORK_ENV_ADVICE = [
45432
47556
  [
@@ -45500,7 +47624,7 @@ const noSecretsInClientCode = defineRule({
45500
47624
  const isServerOnlyScope = isInsideServerOnlyScope(node);
45501
47625
  const trailingSuffix = getIdentifierTrailingWord(variableName);
45502
47626
  const isUiConstant = SECRET_FALSE_POSITIVE_SUFFIXES.has(trailingSuffix);
45503
- if (shouldUseVariableNameHeuristic && !isServerOnlyScope && SECRET_VARIABLE_PATTERN.test(variableName) && !isUiConstant && !isPublicUrlValue(literalValue) && !isPlaceholderValueForVariableHeuristic && !isSelfReferentialSentinelValue(variableName, literalValue) && !isIdentifierLikeKeyNameValue(literalValue) && !isSelfReferentialSentinelValue(variableName, literalValue) && !isIdentifierLikeKeyNameValue(literalValue) && literalValue.length > 24) {
47627
+ if (shouldUseVariableNameHeuristic && !isServerOnlyScope && SECRET_VARIABLE_PATTERN.test(variableName) && !isUiConstant && !isPublicUrlValue(literalValue) && !isPlaceholderValueForVariableHeuristic && !isSelfReferentialSentinelValue(variableName, literalValue) && !isIdentifierLikeKeyNameValue(literalValue) && literalValue.length > 24) {
45504
47628
  context.report({
45505
47629
  node,
45506
47630
  message: `Hardcoding "${variableName}" in client code is a security vulnerability: the secret ships to the browser where anyone can read it.`
@@ -47050,6 +49174,185 @@ const noUnescapedEntities = defineRule({
47050
49174
  } })
47051
49175
  });
47052
49176
  //#endregion
49177
+ //#region src/plugin/utils/read-server-snapshot-boolean.ts
49178
+ const crossFileScopes = /* @__PURE__ */ new WeakMap();
49179
+ const getCrossFileScopes = (programNode) => {
49180
+ const cachedScopes = crossFileScopes.get(programNode);
49181
+ if (cachedScopes) return cachedScopes;
49182
+ const scopes = analyzeScopes(programNode);
49183
+ crossFileScopes.set(programNode, scopes);
49184
+ return scopes;
49185
+ };
49186
+ const symbolIsImmutable = (symbol) => (symbol.kind === "const" || symbol.kind === "function") && symbol.references.every((reference) => reference.flag === "read");
49187
+ const identifierAliasChainIsImmutable = (identifier, scopes, allowImportTerminal = false) => {
49188
+ if (!isNodeOfType(identifier, "Identifier")) return false;
49189
+ const visitedSymbolIds = /* @__PURE__ */ new Set();
49190
+ let symbol = scopes.symbolFor(identifier);
49191
+ while (symbol) {
49192
+ if (symbol.kind === "import") return allowImportTerminal;
49193
+ if (visitedSymbolIds.has(symbol.id) || !symbolIsImmutable(symbol)) return false;
49194
+ visitedSymbolIds.add(symbol.id);
49195
+ if (symbol.kind !== "const" || !symbol.initializer) return symbol.kind === "function";
49196
+ const initializer = stripParenExpression(symbol.initializer);
49197
+ if (!isNodeOfType(initializer, "Identifier")) return true;
49198
+ symbol = scopes.symbolFor(initializer);
49199
+ }
49200
+ return false;
49201
+ };
49202
+ const getImportedHookBinding = (callee, scopes) => {
49203
+ if (!isNodeOfType(callee, "Identifier") || !identifierAliasChainIsImmutable(callee, scopes, true)) return null;
49204
+ const importedSymbol = resolveConstIdentifierAlias(callee, scopes);
49205
+ if (importedSymbol?.kind !== "import") return null;
49206
+ const importDeclaration = importedSymbol.declarationNode.parent;
49207
+ if (!importDeclaration || !isNodeOfType(importDeclaration, "ImportDeclaration")) return null;
49208
+ const source = importDeclaration.source.value;
49209
+ if (typeof source !== "string") return null;
49210
+ const exportedName = resolveImportedExportName(importedSymbol.declarationNode);
49211
+ return exportedName ? {
49212
+ exportedName,
49213
+ source
49214
+ } : null;
49215
+ };
49216
+ const functionBindingIsImmutable = (functionNode, scopes) => {
49217
+ if (isNodeOfType(functionNode, "FunctionDeclaration") && functionNode.id) {
49218
+ const symbol = scopes.symbolFor(functionNode.id);
49219
+ return Boolean(symbol && symbolIsImmutable(symbol));
49220
+ }
49221
+ const expressionRoot = findTransparentExpressionRoot(functionNode);
49222
+ const parentNode = expressionRoot.parent;
49223
+ if (isNodeOfType(parentNode, "ExportDefaultDeclaration")) return true;
49224
+ if (!isNodeOfType(parentNode, "VariableDeclarator") || parentNode.init !== expressionRoot || !isNodeOfType(parentNode.id, "Identifier")) return false;
49225
+ const symbol = scopes.symbolFor(parentNode.id);
49226
+ return Boolean(symbol && symbolIsImmutable(symbol));
49227
+ };
49228
+ const getExactFunctionResultExpression = (functionNode) => {
49229
+ if (!isFunctionLike$1(functionNode) || functionNode.async) return null;
49230
+ if (isNodeOfType(functionNode, "FunctionDeclaration") && functionNode.generator) return null;
49231
+ if (isNodeOfType(functionNode, "FunctionExpression") && functionNode.generator) return null;
49232
+ if (!isNodeOfType(functionNode.body, "BlockStatement")) return functionNode.body;
49233
+ const [returnStatement, additionalReturnStatement] = collectFunctionReturnStatements(functionNode);
49234
+ if (!returnStatement || additionalReturnStatement || !returnStatement.argument) return null;
49235
+ if ([...functionNode.body.body].pop() !== returnStatement) return null;
49236
+ return returnStatement.argument;
49237
+ };
49238
+ const functionHasNoParameters = (functionNode) => isFunctionLike$1(functionNode) && (functionNode.params?.length ?? 0) === 0;
49239
+ const readImmutableBooleanLiteral = (expression, scopes, visitedSymbolIds) => {
49240
+ const unwrappedExpression = stripParenExpression(expression);
49241
+ if (isNodeOfType(unwrappedExpression, "Literal") && typeof unwrappedExpression.value === "boolean") return unwrappedExpression.value;
49242
+ if (!isNodeOfType(unwrappedExpression, "Identifier")) return null;
49243
+ const symbol = scopes.symbolFor(unwrappedExpression);
49244
+ if (!symbol || symbol.kind !== "const" || !symbol.initializer || !symbolIsImmutable(symbol) || visitedSymbolIds.has(symbol.id)) return null;
49245
+ visitedSymbolIds.add(symbol.id);
49246
+ return readImmutableBooleanLiteral(symbol.initializer, scopes, visitedSymbolIds);
49247
+ };
49248
+ const readFunctionLiteralBoolean = (expression, scopes) => {
49249
+ const unwrappedExpression = stripParenExpression(expression);
49250
+ if (isNodeOfType(unwrappedExpression, "Identifier") && !identifierAliasChainIsImmutable(unwrappedExpression, scopes)) return null;
49251
+ const functionNode = resolveExactLocalFunction(unwrappedExpression, scopes);
49252
+ if (!functionNode) return null;
49253
+ const resultExpression = getExactFunctionResultExpression(functionNode);
49254
+ return resultExpression ? readImmutableBooleanLiteral(resultExpression, scopes, /* @__PURE__ */ new Set()) : null;
49255
+ };
49256
+ const readServerSnapshotBooleanInternal = (expression, scopes, visitedSymbolIds, visitedFunctionNodes, currentFilename) => {
49257
+ const unwrappedExpression = stripParenExpression(expression);
49258
+ if (isNodeOfType(unwrappedExpression, "Literal") && typeof unwrappedExpression.value === "boolean") return {
49259
+ hasUseSyncExternalStoreOrigin: false,
49260
+ value: unwrappedExpression.value
49261
+ };
49262
+ if (isNodeOfType(unwrappedExpression, "Identifier")) {
49263
+ const symbol = scopes.symbolFor(unwrappedExpression);
49264
+ if (!symbol || symbol.kind !== "const" || !symbol.initializer || !symbolIsImmutable(symbol) || visitedSymbolIds.has(symbol.id)) return null;
49265
+ visitedSymbolIds.add(symbol.id);
49266
+ return readServerSnapshotBooleanInternal(symbol.initializer, scopes, visitedSymbolIds, visitedFunctionNodes, currentFilename);
49267
+ }
49268
+ if (isNodeOfType(unwrappedExpression, "UnaryExpression") && unwrappedExpression.operator === "!") {
49269
+ const argumentResult = readServerSnapshotBooleanInternal(unwrappedExpression.argument, scopes, visitedSymbolIds, visitedFunctionNodes, currentFilename);
49270
+ return argumentResult ? {
49271
+ hasUseSyncExternalStoreOrigin: argumentResult.hasUseSyncExternalStoreOrigin,
49272
+ value: !argumentResult.value
49273
+ } : null;
49274
+ }
49275
+ if (isNodeOfType(unwrappedExpression, "LogicalExpression") && (unwrappedExpression.operator === "&&" || unwrappedExpression.operator === "||")) {
49276
+ const leftResult = readServerSnapshotBooleanInternal(unwrappedExpression.left, scopes, new Set(visitedSymbolIds), new Set(visitedFunctionNodes), currentFilename);
49277
+ if (leftResult && (unwrappedExpression.operator === "&&" && !leftResult.value || unwrappedExpression.operator === "||" && leftResult.value)) return leftResult;
49278
+ const rightResult = readServerSnapshotBooleanInternal(unwrappedExpression.right, scopes, new Set(visitedSymbolIds), new Set(visitedFunctionNodes), currentFilename);
49279
+ if (rightResult && (unwrappedExpression.operator === "&&" && !rightResult.value || unwrappedExpression.operator === "||" && rightResult.value)) return rightResult;
49280
+ return leftResult && rightResult ? {
49281
+ hasUseSyncExternalStoreOrigin: leftResult.hasUseSyncExternalStoreOrigin || rightResult.hasUseSyncExternalStoreOrigin,
49282
+ value: rightResult.value
49283
+ } : null;
49284
+ }
49285
+ if (!isNodeOfType(unwrappedExpression, "CallExpression")) return null;
49286
+ if (isReactApiCall(unwrappedExpression, "useSyncExternalStore", scopes, { resolveNamedAliases: true })) {
49287
+ const [, , serverSnapshotArgument] = unwrappedExpression.arguments ?? [];
49288
+ if (!serverSnapshotArgument || isNodeOfType(serverSnapshotArgument, "SpreadElement")) return null;
49289
+ const serverSnapshotValue = readFunctionLiteralBoolean(serverSnapshotArgument, scopes);
49290
+ return serverSnapshotValue === null ? null : {
49291
+ hasUseSyncExternalStoreOrigin: true,
49292
+ value: serverSnapshotValue
49293
+ };
49294
+ }
49295
+ if ((unwrappedExpression.arguments?.length ?? 0) !== 0) return null;
49296
+ const callee = stripParenExpression(unwrappedExpression.callee);
49297
+ const importedHookBinding = getImportedHookBinding(callee, scopes);
49298
+ if (importedHookBinding && currentFilename) {
49299
+ const resolvedHook = resolveCrossFileFunctionExportWithFilePath(path.resolve(currentFilename), importedHookBinding.source, importedHookBinding.exportedName);
49300
+ if (resolvedHook && !resolvedHook.filePath.split(path.sep).includes("node_modules")) {
49301
+ const resolvedScopes = getCrossFileScopes(resolvedHook.programNode);
49302
+ if (isReactHookName(componentOrHookDisplayNameForFunction(resolvedHook.functionNode) ?? (importedHookBinding.exportedName === "default" && isNodeOfType(callee, "Identifier") ? callee.name : importedHookBinding.exportedName)) && functionHasNoParameters(resolvedHook.functionNode) && functionBindingIsImmutable(resolvedHook.functionNode, resolvedScopes) && !visitedFunctionNodes.has(resolvedHook.functionNode)) {
49303
+ visitedFunctionNodes.add(resolvedHook.functionNode);
49304
+ const resultExpression = getExactFunctionResultExpression(resolvedHook.functionNode);
49305
+ if (resultExpression) return readServerSnapshotBooleanInternal(resultExpression, resolvedScopes, /* @__PURE__ */ new Set(), visitedFunctionNodes, resolvedHook.filePath);
49306
+ }
49307
+ }
49308
+ return null;
49309
+ }
49310
+ if (!isNodeOfType(callee, "Identifier") || !isReactHookName(callee.name) || !identifierAliasChainIsImmutable(callee, scopes)) return null;
49311
+ const hookFunction = resolveExactLocalFunction(callee, scopes);
49312
+ if (!hookFunction || !functionHasNoParameters(hookFunction) || visitedFunctionNodes.has(hookFunction)) return null;
49313
+ visitedFunctionNodes.add(hookFunction);
49314
+ const resultExpression = getExactFunctionResultExpression(hookFunction);
49315
+ return resultExpression ? readServerSnapshotBooleanInternal(resultExpression, scopes, visitedSymbolIds, visitedFunctionNodes, currentFilename) : null;
49316
+ };
49317
+ const readServerSnapshotBoolean = (expression, scopes, currentFilename) => {
49318
+ const result = readServerSnapshotBooleanInternal(expression, scopes, /* @__PURE__ */ new Set(), /* @__PURE__ */ new Set(), currentFilename);
49319
+ return result?.hasUseSyncExternalStoreOrigin ? result.value : null;
49320
+ };
49321
+ //#endregion
49322
+ //#region src/plugin/utils/is-after-falsy-server-snapshot-early-return.ts
49323
+ const isAfterFalsyServerSnapshotEarlyReturn = (node, componentOrHookNode, scopes, filename) => {
49324
+ const enclosingFunction = findEnclosingFunction$1(node);
49325
+ if (!enclosingFunction || enclosingFunction !== componentOrHookNode && !executesDuringRender(enclosingFunction, scopes) || !isFunctionLike$1(enclosingFunction) || !isNodeOfType(enclosingFunction.body, "BlockStatement")) return false;
49326
+ let currentNode = node;
49327
+ while (currentNode !== enclosingFunction) {
49328
+ const parentNode = currentNode.parent;
49329
+ if (!parentNode) return false;
49330
+ if (isNodeOfType(parentNode, "BlockStatement")) for (const statement of parentNode.body) {
49331
+ if (statement === currentNode) break;
49332
+ if (!isNodeOfType(statement, "IfStatement")) continue;
49333
+ const serverResult = readServerSnapshotBoolean(statement.test, scopes, filename);
49334
+ if (serverResult === true && statementAlwaysExits(statement.consequent)) return true;
49335
+ if (serverResult === false && statement.alternate && statementAlwaysExits(statement.alternate)) return true;
49336
+ }
49337
+ currentNode = parentNode;
49338
+ }
49339
+ return false;
49340
+ };
49341
+ //#endregion
49342
+ //#region src/plugin/utils/is-gated-by-falsy-server-snapshot.ts
49343
+ const isGatedByFalsyServerSnapshot = (node, scopes, filename) => {
49344
+ let currentNode = node;
49345
+ let parentNode = node.parent;
49346
+ while (parentNode) {
49347
+ if (isNodeOfType(parentNode, "LogicalExpression") && parentNode.right === currentNode && (parentNode.operator === "&&" && readServerSnapshotBoolean(parentNode.left, scopes, filename) === false || parentNode.operator === "||" && readServerSnapshotBoolean(parentNode.left, scopes, filename) === true)) return true;
49348
+ if (isNodeOfType(parentNode, "ConditionalExpression") && (parentNode.consequent === currentNode && readServerSnapshotBoolean(parentNode.test, scopes, filename) === false || parentNode.alternate === currentNode && readServerSnapshotBoolean(parentNode.test, scopes, filename) === true)) return true;
49349
+ if (isNodeOfType(parentNode, "IfStatement") && (parentNode.consequent === currentNode && readServerSnapshotBoolean(parentNode.test, scopes, filename) === false || parentNode.alternate === currentNode && readServerSnapshotBoolean(parentNode.test, scopes, filename) === true)) return true;
49350
+ currentNode = parentNode;
49351
+ parentNode = parentNode.parent ?? null;
49352
+ }
49353
+ return false;
49354
+ };
49355
+ //#endregion
47053
49356
  //#region src/plugin/rules/performance/no-unguarded-browser-global-in-render-or-hook-init.ts
47054
49357
  const BROWSER_GLOBAL_NAMES = new Set([
47055
49358
  "window",
@@ -47156,7 +49459,9 @@ const noUnguardedBrowserGlobalInRenderOrHookInit = defineRule({
47156
49459
  if (fileIsEmailTemplate) return;
47157
49460
  if (isGeneratedImageRenderContext(context, findEnclosingJsxOpeningElement(node) ?? node)) return;
47158
49461
  if (isGatedByFalsyInitialState(node, context.scopes)) return;
49462
+ if (isGatedByFalsyServerSnapshot(node, context.scopes, context.filename)) return;
47159
49463
  if (isAfterClientOnlyEarlyReturn(node, componentOrHookNode, context.scopes)) return;
49464
+ if (isAfterFalsyServerSnapshotEarlyReturn(node, componentOrHookNode, context.scopes, context.filename)) return;
47160
49465
  if (isInsideAvailabilityGuard(node, browserGlobalName, context)) return;
47161
49466
  if (isAfterAvailabilityEarlyExit(node, componentOrHookNode, browserGlobalName, context)) return;
47162
49467
  reportedNodes.add(node);
@@ -50484,10 +52789,9 @@ const isSameApplicationChannelInstance = (targetText, fileContent) => {
50484
52789
  return new RegExp(`(?<![\\w$.])${escapeRegExp(receiverRoot)}\\s*${SAME_APPLICATION_CHANNEL_CONSTRUCTOR_SOURCE}`).test(fileContent);
50485
52790
  };
50486
52791
  const WORKER_FILE_PATH_PATTERN = /worker/i;
50487
- const getNodeStartIndex = (node) => "start" in node && typeof node.start === "number" ? node.start : -1;
50488
52792
  const getNodeText = (content, node) => {
50489
52793
  const startIndex = getNodeStartIndex(node);
50490
- const endIndex = "end" in node && typeof node.end === "number" ? node.end : -1;
52794
+ const endIndex = getNodeEndIndex(node);
50491
52795
  if (startIndex < 0 || endIndex < 0) return "";
50492
52796
  return content.slice(startIndex, endIndex);
50493
52797
  };
@@ -50884,17 +53188,6 @@ const preferEs6Class = defineRule({
50884
53188
  }
50885
53189
  });
50886
53190
  //#endregion
50887
- //#region src/plugin/utils/is-jsx-element-or-fragment.ts
50888
- /**
50889
- * Type-guard for the two single-node JSX output forms: `JSXElement`
50890
- * (`<Foo />`) and `JSXFragment` (`<>…</>`). Canonical home for the
50891
- * `isNodeOfType(x, "JSXElement") || isNodeOfType(x, "JSXFragment")` check
50892
- * that many rules otherwise inline. Does NOT unwrap parens / TS wrappers —
50893
- * callers that need the semantic expression should `stripParenExpression`
50894
- * first.
50895
- */
50896
- const isJsxElementOrFragment = (node) => Boolean(node && (isNodeOfType(node, "JSXElement") || isNodeOfType(node, "JSXFragment")));
50897
- //#endregion
50898
53191
  //#region src/plugin/rules/architecture/prefer-explicit-variants.ts
50899
53192
  const resolveBooleanPropTestName = (testNode, booleanPropBindings) => {
50900
53193
  let identifierNode = stripParenExpression(testNode);
@@ -51359,20 +53652,51 @@ const preferModuleScopePureFunction = defineRule({
51359
53652
  }
51360
53653
  });
51361
53654
  //#endregion
53655
+ //#region src/plugin/utils/get-require-call-source.ts
53656
+ const getRequireCallSource = (expression) => {
53657
+ const unwrappedExpression = stripParenExpression(expression);
53658
+ if (isNodeOfType(unwrappedExpression, "MemberExpression")) return getRequireCallSource(unwrappedExpression.object);
53659
+ if (!isNodeOfType(unwrappedExpression, "CallExpression")) return null;
53660
+ if (!isNodeOfType(unwrappedExpression.callee, "Identifier") || unwrappedExpression.callee.name !== "require") return null;
53661
+ const [firstArgument] = unwrappedExpression.arguments ?? [];
53662
+ if (!firstArgument || !isNodeOfType(firstArgument, "Literal")) return null;
53663
+ return typeof firstArgument.value === "string" ? firstArgument.value : null;
53664
+ };
53665
+ //#endregion
53666
+ //#region src/plugin/utils/is-proven-node-crypto-namespace-reference.ts
53667
+ const NODE_CRYPTO_MODULE_SOURCES = new Set(["crypto", "node:crypto"]);
53668
+ const isProvenNodeCryptoNamespaceReference = (expression, scopes) => {
53669
+ const identifier = stripParenExpression(expression);
53670
+ if (!isNodeOfType(identifier, "Identifier")) return false;
53671
+ const symbol = resolveConstIdentifierAlias(identifier, scopes);
53672
+ if (!symbol) return false;
53673
+ if (symbol.kind === "import") {
53674
+ const importBinding = getImportBindingForName(identifier, symbol.name);
53675
+ return Boolean(importBinding && NODE_CRYPTO_MODULE_SOURCES.has(importBinding.source));
53676
+ }
53677
+ return Boolean(symbol.kind === "const" && symbol.initializer && NODE_CRYPTO_MODULE_SOURCES.has(getRequireCallSource(symbol.initializer) ?? ""));
53678
+ };
53679
+ //#endregion
51362
53680
  //#region src/plugin/rules/architecture/prefer-module-scope-static-value.ts
51363
53681
  const MUTATING_RECEIVER_METHOD_NAMES = new Set([...MUTATING_ARRAY_METHODS, ...MUTATING_COLLECTION_METHODS]);
51364
53682
  const isMutationContext = (referenceIdentifier) => {
51365
- const parent = referenceIdentifier.parent;
51366
- if (!parent) return false;
51367
- if (isNodeOfType(parent, "AssignmentExpression") && parent.left === referenceIdentifier) return true;
51368
- if (isNodeOfType(parent, "UpdateExpression") && parent.argument === referenceIdentifier) return true;
51369
- if (isNodeOfType(parent, "MemberExpression") && parent.object === referenceIdentifier) {
51370
- const grandparent = parent.parent;
51371
- if (!grandparent) return false;
51372
- if (isNodeOfType(grandparent, "AssignmentExpression") && grandparent.left === parent) return true;
51373
- if (isNodeOfType(grandparent, "UpdateExpression") && grandparent.argument === parent) return true;
51374
- if (isNodeOfType(grandparent, "UnaryExpression") && grandparent.operator === "delete" && grandparent.argument === parent) return true;
51375
- if (isNodeOfType(grandparent, "CallExpression") && grandparent.callee === parent && !parent.computed && isNodeOfType(parent.property, "Identifier") && MUTATING_RECEIVER_METHOD_NAMES.has(parent.property.name)) return true;
53683
+ let mutationTarget = referenceIdentifier;
53684
+ let receiverMethodName = null;
53685
+ while (mutationTarget.parent) {
53686
+ const parent = mutationTarget.parent;
53687
+ if (TRANSPARENT_EXPRESSION_WRAPPER_TYPES.has(parent.type) && "expression" in parent && parent.expression === mutationTarget) {
53688
+ mutationTarget = parent;
53689
+ continue;
53690
+ }
53691
+ if (isNodeOfType(parent, "MemberExpression") && parent.object === mutationTarget) {
53692
+ receiverMethodName = getStaticPropertyName(parent);
53693
+ mutationTarget = parent;
53694
+ continue;
53695
+ }
53696
+ if (isNodeOfType(parent, "AssignmentExpression") && parent.left === mutationTarget) return true;
53697
+ if (isNodeOfType(parent, "UpdateExpression") && parent.argument === mutationTarget) return true;
53698
+ if (isNodeOfType(parent, "UnaryExpression") && parent.operator === "delete" && parent.argument === mutationTarget) return true;
53699
+ return Boolean(isNodeOfType(parent, "CallExpression") && parent.callee === mutationTarget && MUTATING_RECEIVER_METHOD_NAMES.has(receiverMethodName ?? ""));
51376
53700
  }
51377
53701
  return false;
51378
53702
  };
@@ -51472,9 +53796,9 @@ const isImpureCall = (node, scopes) => {
51472
53796
  const callee = node.callee;
51473
53797
  if (isNodeOfType(callee, "Identifier")) return isImpureBareCallee(callee, scopes);
51474
53798
  if (!isNodeOfType(callee, "MemberExpression") || callee.computed) return false;
51475
- if (!isNodeOfType(callee.object, "Identifier")) return false;
51476
53799
  if (!isNodeOfType(callee.property, "Identifier")) return false;
51477
- return Boolean(IMPURE_MEMBER_RECEIVERS.get(callee.object.name)?.has(callee.property.name));
53800
+ for (const [receiverName, receiverMethodNames] of IMPURE_MEMBER_RECEIVERS) if (receiverMethodNames.has(callee.property.name) && (isProvenGlobalNamespaceReference(callee.object, receiverName, scopes) || receiverName === "crypto" && isProvenNodeCryptoNamespaceReference(callee.object, scopes))) return true;
53801
+ return false;
51478
53802
  };
51479
53803
  const containsImpureExpression = (expression, scopes) => {
51480
53804
  let foundImpure = false;
@@ -51749,6 +54073,15 @@ const STABLE_REACT_HOOK_VALUE_NAMES = new Set([
51749
54073
  "useState",
51750
54074
  "useTransition"
51751
54075
  ]);
54076
+ const REGISTRATION_METHOD_BY_RELEASE_METHOD = new Map([
54077
+ ["off", "on"],
54078
+ ["removeEventListener", "addEventListener"],
54079
+ ["removeListener", "addListener"],
54080
+ ["unlisten", "listen"],
54081
+ ["unsub", "sub"],
54082
+ ["unsubscribe", "subscribe"],
54083
+ ["unwatch", "watch"]
54084
+ ]);
51752
54085
  const isStableReactHookDependency = (dependency, context) => {
51753
54086
  const unwrappedDependency = stripParenExpression(dependency);
51754
54087
  if (!isNodeOfType(unwrappedDependency, "Identifier")) return false;
@@ -51814,30 +54147,87 @@ const isCallExpressionWithSubHandlerCallee = (callExpression) => {
51814
54147
  if (isNodeOfType(callee, "MemberExpression") && isNodeOfType(callee.property, "Identifier") && SUBSCRIPTION_METHOD_NAMES.has(callee.property.name)) return true;
51815
54148
  return false;
51816
54149
  };
51817
- const findSubHandlerForEnclosingFunction = (enclosingFunction, effectCallback) => {
54150
+ const getStaticMemberCallMethodName = (callExpression) => {
54151
+ if (!isNodeOfType(callExpression, "CallExpression")) return null;
54152
+ const callee = callExpression.callee;
54153
+ return isNodeOfType(callee, "MemberExpression") && !callee.computed && isNodeOfType(callee.property, "Identifier") ? callee.property.name : null;
54154
+ };
54155
+ const getCallArgumentUse = (reference) => {
54156
+ const argument = findTransparentExpressionRoot(reference);
54157
+ const parent = argument.parent;
54158
+ if (!isNodeOfType(parent, "CallExpression")) return null;
54159
+ const argumentIndex = (parent.arguments ?? []).findIndex((candidateArgument) => candidateArgument === argument);
54160
+ return argumentIndex === -1 ? null : {
54161
+ callExpression: parent,
54162
+ argumentIndex
54163
+ };
54164
+ };
54165
+ const isMatchingRegistrationAndRelease = (registration, release, context) => {
54166
+ const releaseMethodName = getStaticMemberCallMethodName(release.callExpression);
54167
+ const expectedRegistrationMethod = releaseMethodName ? REGISTRATION_METHOD_BY_RELEASE_METHOD.get(releaseMethodName) : null;
54168
+ if (getStaticMemberCallMethodName(registration.callExpression) !== expectedRegistrationMethod) return false;
54169
+ if (registration.argumentIndex !== release.argumentIndex) return false;
54170
+ const registrationCallee = registration.callExpression.callee;
54171
+ const releaseCallee = release.callExpression.callee;
54172
+ if (!isNodeOfType(registrationCallee, "MemberExpression") || !isNodeOfType(releaseCallee, "MemberExpression")) return false;
54173
+ const registrationReceiverKey = resolveExpressionKey$1(registrationCallee.object, context);
54174
+ if (registrationReceiverKey === null || registrationReceiverKey !== resolveExpressionKey$1(releaseCallee.object, context)) return false;
54175
+ const registrationArguments = registration.callExpression.arguments ?? [];
54176
+ const releaseArguments = release.callExpression.arguments ?? [];
54177
+ if (registrationArguments.length !== releaseArguments.length) return false;
54178
+ return registrationArguments.every((registrationArgument, argumentIndex) => {
54179
+ if (argumentIndex === registration.argumentIndex) return true;
54180
+ const registrationArgumentKey = resolveExpressionKey$1(registrationArgument, context);
54181
+ return registrationArgumentKey !== null && registrationArgumentKey === resolveExpressionKey$1(releaseArguments[argumentIndex], context);
54182
+ });
54183
+ };
54184
+ const findExclusiveSubHandlerCall = (enclosingFunction, context) => {
51818
54185
  const directParent = enclosingFunction.parent;
51819
54186
  if (isNodeOfType(directParent, "CallExpression") && (directParent.arguments ?? []).some((arg) => arg === enclosingFunction) && isCallExpressionWithSubHandlerCallee(directParent)) return directParent;
51820
- const localName = getFunctionBindingName$1(enclosingFunction);
51821
- if (localName === null) return null;
51822
- let matchingSubHandlerCall = null;
51823
- walkAst(effectCallback, (child) => {
51824
- if (matchingSubHandlerCall) return false;
51825
- if (!isNodeOfType(child, "CallExpression")) return;
51826
- if (!isCallExpressionWithSubHandlerCallee(child)) return;
51827
- for (const argument of child.arguments ?? []) if (isNodeOfType(argument, "Identifier") && argument.name === localName) {
51828
- matchingSubHandlerCall = child;
51829
- return false;
54187
+ const bindingIdentifier = getFunctionBindingIdentifier$1(enclosingFunction);
54188
+ if (!bindingIdentifier) return null;
54189
+ let bindingSymbol = context.scopes.symbolFor(bindingIdentifier);
54190
+ if (isNodeOfType(enclosingFunction, "FunctionDeclaration")) {
54191
+ let bindingScope = context.scopes.scopeFor(enclosingFunction);
54192
+ bindingSymbol = null;
54193
+ while (bindingScope && !bindingSymbol) {
54194
+ bindingSymbol = bindingScope.symbols.find((candidateSymbol) => candidateSymbol.declarationNode === enclosingFunction) ?? null;
54195
+ bindingScope = bindingScope.parent;
51830
54196
  }
51831
- });
51832
- return matchingSubHandlerCall;
54197
+ }
54198
+ if (!bindingSymbol) return null;
54199
+ const registrations = [];
54200
+ const releases = [];
54201
+ for (const reference of bindingSymbol.references) {
54202
+ if (isAstDescendant(reference.identifier, enclosingFunction)) continue;
54203
+ if (reference.identifier === bindingIdentifier) continue;
54204
+ if (reference.flag !== "read") return null;
54205
+ const receivingUse = getCallArgumentUse(reference.identifier);
54206
+ if (!receivingUse) return null;
54207
+ if (isCallExpressionWithSubHandlerCallee(receivingUse.callExpression)) {
54208
+ registrations.push(receivingUse);
54209
+ continue;
54210
+ }
54211
+ const methodName = getStaticMemberCallMethodName(receivingUse.callExpression);
54212
+ if (!methodName || !REGISTRATION_METHOD_BY_RELEASE_METHOD.has(methodName)) return null;
54213
+ releases.push(receivingUse);
54214
+ }
54215
+ if (releases.some((release) => !registrations.some((registration) => isMatchingRegistrationAndRelease(registration, release, context)))) return null;
54216
+ return registrations[0]?.callExpression ?? null;
51833
54217
  };
51834
- const classifyCallableReadsInsideEffect = (callableName, effectCallback) => {
54218
+ const classifyCallableReadsInsideEffect = (callableIdentifier, effectCallback, context) => {
51835
54219
  let hasAnyRead = false;
51836
54220
  let allReadsAreInSubHandlers = true;
51837
54221
  let firstSubHandlerName = null;
54222
+ const callableSymbol = context.scopes.symbolFor(callableIdentifier);
54223
+ if (!callableSymbol) return {
54224
+ hasAnyRead,
54225
+ allReadsAreInSubHandlers,
54226
+ firstSubHandlerName
54227
+ };
51838
54228
  walkAst(effectCallback, (child) => {
51839
54229
  if (!isNodeOfType(child, "Identifier")) return;
51840
- if (child.name !== callableName) return;
54230
+ if (context.scopes.symbolFor(child)?.id !== callableSymbol.id) return;
51841
54231
  const parent = child.parent;
51842
54232
  if (isNodeOfType(parent, "ArrayExpression")) return;
51843
54233
  if (isNodeOfType(parent, "MemberExpression") && !parent.computed && parent.property === child) return;
@@ -51848,7 +54238,7 @@ const classifyCallableReadsInsideEffect = (callableName, effectCallback) => {
51848
54238
  allReadsAreInSubHandlers = false;
51849
54239
  return;
51850
54240
  }
51851
- const subHandlerCall = findSubHandlerForEnclosingFunction(enclosingFunction, effectCallback);
54241
+ const subHandlerCall = findExclusiveSubHandlerCall(enclosingFunction, context);
51852
54242
  if (!subHandlerCall) {
51853
54243
  allReadsAreInSubHandlers = false;
51854
54244
  return;
@@ -51891,7 +54281,7 @@ const preferUseEffectEvent = defineRule({
51891
54281
  const isFunctionTypedPropDep = propStackTracker.isPropName(depName) && REACT_HANDLER_PROP_PATTERN.test(depName);
51892
54282
  const isFunctionTypedLocalDep = potentiallyChangingCallbackBindings.has(depName);
51893
54283
  if (!isFunctionTypedPropDep && !isFunctionTypedLocalDep) continue;
51894
- const classification = classifyCallableReadsInsideEffect(depName, callback);
54284
+ const classification = classifyCallableReadsInsideEffect(depElement, callback, context);
51895
54285
  if (!classification.hasAnyRead) continue;
51896
54286
  if (!classification.allReadsAreInSubHandlers) continue;
51897
54287
  const subHandlerLabel = classification.firstSubHandlerName ? `\`${classification.firstSubHandlerName}\`` : "an async sub-handler";
@@ -53206,12 +55596,7 @@ const DOMPURIFY_MODULES = new Set(["dompurify", "isomorphic-dompurify"]);
53206
55596
  const REACT_MARKDOWN_NAMED_EXPORTS = new Set(["MarkdownAsync", "MarkdownHooks"]);
53207
55597
  const REACT_MARKDOWN_NAMESPACE_EXPORTS = new Set(["default", ...REACT_MARKDOWN_NAMED_EXPORTS]);
53208
55598
  const DEFAULT_EXPORT_NAMES = new Set(["default"]);
53209
- const getImportDeclaration = (symbol) => {
53210
- if (symbol.kind !== "import") return null;
53211
- const importDeclaration = symbol.declarationNode.parent;
53212
- return isNodeOfType(importDeclaration, "ImportDeclaration") ? importDeclaration : null;
53213
- };
53214
- const isImportFromModule = (symbol, moduleName) => getImportDeclaration(symbol)?.source.value === moduleName;
55599
+ const isImportFromModule = (symbol, moduleName) => getImportDeclarationForSymbol(symbol)?.source.value === moduleName;
53215
55600
  const isDefaultImportSymbol = (symbol, moduleName) => {
53216
55601
  if (!isImportFromModule(symbol, moduleName)) return false;
53217
55602
  return isNodeOfType(symbol.declarationNode, "ImportDefaultSpecifier") || getImportedName(symbol.declarationNode) === "default";
@@ -53301,7 +55686,7 @@ const getAttributeExpression = (attribute) => {
53301
55686
  const isDomPurifyNamespace = (node, scopes) => {
53302
55687
  const symbol = resolveImportedIdentifier(node, scopes);
53303
55688
  if (!symbol) return false;
53304
- const importDeclaration = getImportDeclaration(symbol);
55689
+ const importDeclaration = getImportDeclarationForSymbol(symbol);
53305
55690
  if (!importDeclaration || !DOMPURIFY_MODULES.has(String(importDeclaration.source.value))) return false;
53306
55691
  return isNodeOfType(symbol.declarationNode, "ImportDefaultSpecifier") || isNodeOfType(symbol.declarationNode, "ImportNamespaceSpecifier") || getImportedName(symbol.declarationNode) === "default";
53307
55692
  };
@@ -56243,16 +58628,6 @@ const rnListCallbackPerRow = defineRule({
56243
58628
  }
56244
58629
  });
56245
58630
  //#endregion
56246
- //#region src/plugin/utils/get-require-call-source.ts
56247
- const getRequireCallSource = (expression) => {
56248
- if (isNodeOfType(expression, "MemberExpression")) return getRequireCallSource(expression.object);
56249
- if (!isNodeOfType(expression, "CallExpression")) return null;
56250
- if (!isNodeOfType(expression.callee, "Identifier") || expression.callee.name !== "require") return null;
56251
- const [firstArgument] = expression.arguments ?? [];
56252
- if (!firstArgument || !isNodeOfType(firstArgument, "Literal")) return null;
56253
- return typeof firstArgument.value === "string" ? firstArgument.value : null;
56254
- };
56255
- //#endregion
56256
58631
  //#region src/plugin/utils/get-initializer-module-source.ts
56257
58632
  const getInitializerModuleSource = (contextNode, initializer) => {
56258
58633
  const requireSource = getRequireCallSource(initializer);
@@ -56285,7 +58660,7 @@ const resolveImportedRecyclerName = (node, localName, options) => {
56285
58660
  const jsxMemberObjectName = options?.allowNamespaceMemberAccess ? getJsxMemberObjectName(node) : null;
56286
58661
  for (const [canonicalName, packageSources] of Object.entries(RECYCLABLE_LIST_PACKAGES)) {
56287
58662
  if (jsxMemberObjectName !== null) {
56288
- if (localName === canonicalName && packageSources.some((packageSource) => isNamespaceImportFromModule(node, jsxMemberObjectName, packageSource))) return canonicalName;
58663
+ if (localName === canonicalName && packageSources.some((packageSource) => isNamespaceImportFromModule$1(node, jsxMemberObjectName, packageSource))) return canonicalName;
56289
58664
  continue;
56290
58665
  }
56291
58666
  if (packageSources.some((packageSource) => getImportedNameFromModule(node, localName, packageSource) === canonicalName)) return canonicalName;
@@ -57544,7 +59919,7 @@ const isNamedImportOf = (contextNode, localName, componentName) => {
57544
59919
  return false;
57545
59920
  };
57546
59921
  const isExpoUiNamespaceImport = (contextNode, localName) => {
57547
- for (const moduleSource of EXPO_UI_MODULE_SOURCES) if (isNamespaceImportFromModule(contextNode, localName, moduleSource)) return true;
59922
+ for (const moduleSource of EXPO_UI_MODULE_SOURCES) if (isNamespaceImportFromModule$1(contextNode, localName, moduleSource)) return true;
57548
59923
  return false;
57549
59924
  };
57550
59925
  const isExpoUiComponentElement = (openingElement, contextNode, componentName) => {
@@ -58692,6 +61067,7 @@ const roleHasRequiredAriaProps = defineRule({
58692
61067
  recommendation: "Add every required `aria-*` attribute so assistive tech can expose the role's state correctly.",
58693
61068
  category: "Accessibility",
58694
61069
  create: (context) => ({ JSXOpeningElement(node) {
61070
+ if (isLocalTestScaffoldJsx(node, context)) return;
58695
61071
  const elementType = getElementType(node, context.settings);
58696
61072
  if (!HTML_TAGS.has(elementType)) return;
58697
61073
  const roleAttribute = hasJsxPropIgnoreCase(node.attributes, "role");
@@ -61827,6 +64203,7 @@ const roleSupportsAriaProps = defineRule({
61827
64203
  recommendation: "Only use `aria-*` attributes that the element's role supports.",
61828
64204
  category: "Accessibility",
61829
64205
  create: (context) => ({ JSXOpeningElement(node) {
64206
+ if (isLocalTestScaffoldJsx(node, context)) return;
61830
64207
  let ariaAttributes = null;
61831
64208
  for (const attribute of node.attributes) {
61832
64209
  if (!isNodeOfType(attribute, "JSXAttribute")) continue;
@@ -62572,6 +64949,109 @@ const isDeferrableSideEffectCall = (objectName, methodName) => {
62572
64949
  if (ANALYTICS_DEFERRABLE_OBJECTS.has(objectName)) return ANALYTICS_DEFERRABLE_METHODS.has(methodName);
62573
64950
  return false;
62574
64951
  };
64952
+ const NEXT_SERVER_SOURCE = "next/server";
64953
+ const NEXT_AFTER_EXPORT_NAMES = new Set(["after", "unstable_after"]);
64954
+ const isNextAfterImportSymbol = (symbol, contextNode) => {
64955
+ if (symbol.kind !== "import") return false;
64956
+ const importBinding = getImportBindingForName(contextNode, symbol.name);
64957
+ return Boolean(importBinding && importBinding.source === NEXT_SERVER_SOURCE && !importBinding.isNamespace && importBinding.exportedName && NEXT_AFTER_EXPORT_NAMES.has(importBinding.exportedName));
64958
+ };
64959
+ const isDirectObjectPatternBinding = (symbol) => {
64960
+ if (!isNodeOfType(symbol.declarationNode, "VariableDeclarator")) return false;
64961
+ if (!isNodeOfType(symbol.declarationNode.id, "ObjectPattern")) return false;
64962
+ let bindingNode = symbol.bindingIdentifier;
64963
+ if (isNodeOfType(bindingNode.parent, "AssignmentPattern") && bindingNode.parent.left === bindingNode) bindingNode = bindingNode.parent;
64964
+ const property = bindingNode.parent;
64965
+ return Boolean(isNodeOfType(property, "Property") && property.value === bindingNode && property.parent === symbol.declarationNode.id);
64966
+ };
64967
+ const isNextServerNamespace = (expression, contextNode, scopes) => {
64968
+ let candidate = stripParenExpression(expression);
64969
+ const visitedSymbolIds = /* @__PURE__ */ new Set();
64970
+ while (isNodeOfType(candidate, "Identifier")) {
64971
+ const symbol = scopes.symbolFor(candidate);
64972
+ if (!symbol || visitedSymbolIds.has(symbol.id)) return false;
64973
+ if (symbol.kind === "import") {
64974
+ const importBinding = getImportBindingForName(contextNode, symbol.name);
64975
+ return Boolean(importBinding?.source === NEXT_SERVER_SOURCE && importBinding.isNamespace);
64976
+ }
64977
+ if (symbol.kind !== "const" || !symbol.initializer || !isNodeOfType(symbol.declarationNode, "VariableDeclarator") || symbol.declarationNode.id !== symbol.bindingIdentifier) return false;
64978
+ visitedSymbolIds.add(symbol.id);
64979
+ candidate = stripParenExpression(symbol.initializer);
64980
+ }
64981
+ return false;
64982
+ };
64983
+ const isNextAfterCallee = (callee, contextNode, scopes, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
64984
+ const candidate = stripParenExpression(callee);
64985
+ if (isNodeOfType(candidate, "MemberExpression")) {
64986
+ const propertyName = getStaticPropertyKeyName(candidate, { allowComputedString: true });
64987
+ return Boolean(propertyName && NEXT_AFTER_EXPORT_NAMES.has(propertyName) && isNextServerNamespace(candidate.object, contextNode, scopes));
64988
+ }
64989
+ if (!isNodeOfType(candidate, "Identifier")) return false;
64990
+ const symbol = scopes.symbolFor(candidate);
64991
+ if (!symbol || visitedSymbolIds.has(symbol.id)) return false;
64992
+ if (isNextAfterImportSymbol(symbol, contextNode)) return true;
64993
+ const destructuredPropertyName = getDestructuredBindingPropertyName(symbol.bindingIdentifier);
64994
+ if (symbol.kind === "const" && symbol.initializer && isDirectObjectPatternBinding(symbol) && destructuredPropertyName && NEXT_AFTER_EXPORT_NAMES.has(destructuredPropertyName)) return isNextServerNamespace(symbol.initializer, contextNode, scopes);
64995
+ if (symbol.kind !== "const" || !symbol.initializer || !isNodeOfType(symbol.declarationNode, "VariableDeclarator") || symbol.declarationNode.id !== symbol.bindingIdentifier) return false;
64996
+ visitedSymbolIds.add(symbol.id);
64997
+ return isNextAfterCallee(symbol.initializer, contextNode, scopes, visitedSymbolIds);
64998
+ };
64999
+ const getDirectArgumentCall = (expression) => {
65000
+ const expressionRoot = findTransparentExpressionRoot(expression);
65001
+ const parent = expressionRoot.parent;
65002
+ if (!isNodeOfType(parent, "CallExpression")) return null;
65003
+ return parent.arguments[0] === expressionRoot ? parent : null;
65004
+ };
65005
+ const isScheduledByNextAfter = (expression, scopes) => {
65006
+ const callExpression = getDirectArgumentCall(expression);
65007
+ return Boolean(callExpression && isNextAfterCallee(callExpression.callee, callExpression, scopes));
65008
+ };
65009
+ const getFunctionBindingSymbol = (functionNode, scopes) => {
65010
+ if (isNodeOfType(functionNode, "FunctionDeclaration") && functionNode.id) return scopes.scopeFor(functionNode).symbols.find((symbol) => symbol.declarationNode === functionNode) ?? null;
65011
+ const functionRoot = findTransparentExpressionRoot(functionNode);
65012
+ const parent = functionRoot.parent;
65013
+ if (!isNodeOfType(parent, "VariableDeclarator") || parent.init !== functionRoot || !isNodeOfType(parent.id, "Identifier")) return null;
65014
+ return scopes.symbolFor(parent.id);
65015
+ };
65016
+ const isDirectlyExported = (symbol) => {
65017
+ let declaration = symbol.declarationNode;
65018
+ if (isNodeOfType(declaration, "VariableDeclarator")) declaration = declaration.parent;
65019
+ return Boolean(declaration?.parent && (isNodeOfType(declaration.parent, "ExportNamedDeclaration") || isNodeOfType(declaration.parent, "ExportDefaultDeclaration")));
65020
+ };
65021
+ const isLexicallyInsideFunction = (node, functionNode) => {
65022
+ let enclosingFunction = findEnclosingFunction$1(node);
65023
+ while (enclosingFunction) {
65024
+ if (enclosingFunction === functionNode) return true;
65025
+ enclosingFunction = findEnclosingFunction$1(enclosingFunction);
65026
+ }
65027
+ return false;
65028
+ };
65029
+ const isExclusivelyScheduledByNextAfter = (functionNode, scopes, visitedFunctionSymbolIds) => {
65030
+ if (isScheduledByNextAfter(functionNode, scopes)) return true;
65031
+ const functionSymbol = getFunctionBindingSymbol(functionNode, scopes);
65032
+ if (!functionSymbol || isDirectlyExported(functionSymbol) || visitedFunctionSymbolIds.has(functionSymbol.id)) return false;
65033
+ const nextVisitedFunctionSymbolIds = new Set(visitedFunctionSymbolIds).add(functionSymbol.id);
65034
+ let hasAfterUse = false;
65035
+ for (const reference of functionSymbol.references) {
65036
+ if (reference.flag !== "read") return false;
65037
+ if (isLexicallyInsideFunction(reference.identifier, functionNode)) continue;
65038
+ if (isScheduledByNextAfter(reference.identifier, scopes)) {
65039
+ hasAfterUse = true;
65040
+ continue;
65041
+ }
65042
+ if (!isInsideNextAfterCallback(reference.identifier, scopes, nextVisitedFunctionSymbolIds)) return false;
65043
+ hasAfterUse = true;
65044
+ }
65045
+ return hasAfterUse;
65046
+ };
65047
+ const isInsideNextAfterCallback = (node, scopes, visitedFunctionSymbolIds = /* @__PURE__ */ new Set()) => {
65048
+ let enclosingFunction = findEnclosingFunction$1(node);
65049
+ while (enclosingFunction) {
65050
+ if (isExclusivelyScheduledByNextAfter(enclosingFunction, scopes, visitedFunctionSymbolIds)) return true;
65051
+ enclosingFunction = findEnclosingFunction$1(enclosingFunction);
65052
+ }
65053
+ return false;
65054
+ };
62575
65055
  const serverAfterNonblocking = defineRule({
62576
65056
  id: "server-after-nonblocking",
62577
65057
  title: "Blocking side effect before response",
@@ -62606,6 +65086,7 @@ const serverAfterNonblocking = defineRule({
62606
65086
  if (!objectName) return;
62607
65087
  const methodName = node.callee.property.name;
62608
65088
  if (!isDeferrableSideEffectCall(objectName, methodName)) return;
65089
+ if (isInsideNextAfterCallback(node, context.scopes)) return;
62609
65090
  context.report({
62610
65091
  node,
62611
65092
  message: `${objectName}.${methodName}() runs before the response, so your users wait longer for it.`
@@ -63031,9 +65512,35 @@ const serverDedupProps = defineRule({
63031
65512
  });
63032
65513
  //#endregion
63033
65514
  //#region src/plugin/rules/server/server-fetch-without-revalidate.ts
63034
- const isFetchCall = (node) => {
65515
+ const isGlobalThisFetchMember = (node, context) => {
65516
+ const memberExpression = stripParenExpression(node);
65517
+ if (!isNodeOfType(memberExpression, "MemberExpression")) return false;
65518
+ const receiver = stripParenExpression(memberExpression.object);
65519
+ return getStaticPropertyName(memberExpression) === "fetch" && isNodeOfType(receiver, "Identifier") && receiver.name === "globalThis" && context.scopes.isGlobalReference(receiver);
65520
+ };
65521
+ const isGlobalThisIdentifier = (node, context) => {
65522
+ const expression = stripParenExpression(node);
65523
+ return isNodeOfType(expression, "Identifier") && expression.name === "globalThis" && context.scopes.isGlobalReference(expression);
65524
+ };
65525
+ const isGlobalFetchDestructuringBinding = (symbolBinding, declaration, context) => isNodeOfType(declaration.id, "ObjectPattern") && Boolean(declaration.id.properties.some((property) => isNodeOfType(property, "Property") && property.value === symbolBinding && getStaticPropertyKeyName(property, { allowComputedString: true }) === "fetch") && declaration.init && isGlobalThisIdentifier(declaration.init, context));
65526
+ const isExactGlobalFetchValue = (node, context, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
65527
+ const expression = stripParenExpression(node);
65528
+ if (isGlobalThisFetchMember(expression, context)) return true;
65529
+ if (!isNodeOfType(expression, "Identifier")) return false;
65530
+ if (expression.name === "fetch" && context.scopes.isGlobalReference(expression)) return true;
65531
+ const symbol = context.scopes.symbolFor(expression);
65532
+ if (!symbol || symbol.kind !== "const" || visitedSymbolIds.has(symbol.id)) return false;
65533
+ if (!isNodeOfType(symbol.declarationNode, "VariableDeclarator")) return false;
65534
+ if (isGlobalFetchDestructuringBinding(symbol.bindingIdentifier, symbol.declarationNode, context)) return true;
65535
+ if (symbol.declarationNode.id !== symbol.bindingIdentifier || !symbol.initializer) return false;
65536
+ visitedSymbolIds.add(symbol.id);
65537
+ return isExactGlobalFetchValue(symbol.initializer, context, visitedSymbolIds);
65538
+ };
65539
+ const isFetchCall = (node, context) => {
63035
65540
  if (!isNodeOfType(node, "CallExpression")) return false;
63036
- return isNodeOfType(node.callee, "Identifier") && node.callee.name === "fetch";
65541
+ const callee = stripParenExpression(node.callee);
65542
+ if (!isNodeOfType(callee, "Identifier") || callee.name !== "fetch") return false;
65543
+ return isExactGlobalFetchValue(callee, context);
63037
65544
  };
63038
65545
  const getPropertyKeyName$1 = (property) => {
63039
65546
  if (!isNodeOfType(property, "Property")) return null;
@@ -63079,7 +65586,7 @@ const serverFetchWithoutRevalidate = defineRule({
63079
65586
  },
63080
65587
  CallExpression(node) {
63081
65588
  if (!isServerSideFile) return;
63082
- if (!isFetchCall(node)) return;
65589
+ if (!isFetchCall(node, context)) return;
63083
65590
  if (isMutatingFetchCall(node)) return;
63084
65591
  const optionsArg = node.arguments?.[1];
63085
65592
  if (optionsArg) {
@@ -63854,17 +66361,34 @@ const stylePropObject = defineRule({
63854
66361
  };
63855
66362
  }
63856
66363
  });
66364
+ //#endregion
66365
+ //#region src/plugin/rules/security-scan/utils/has-use-server-directive-in-content.ts
66366
+ const hasUseServerDirectiveInContent = (content, relativePath = "source.tsx") => {
66367
+ const programNode = parseSourceText({
66368
+ filename: relativePath,
66369
+ sourceText: content,
66370
+ shouldAttachParentReferences: false
66371
+ });
66372
+ return programNode === null ? false : hasDirective(programNode, "use server");
66373
+ };
66374
+ //#endregion
66375
+ //#region src/plugin/rules/security-scan/supabase-client-owned-authz-field.ts
66376
+ const scanSupabaseClientOwnedAuthzField = scanByPattern({
66377
+ shouldScan: (file) => isClientSourcePath(file.relativePath),
66378
+ pattern: /\b(?:ownerId|ownerID|creatorId|creatorID|userId|userID|uid|providerId|providerID|orgId|orgID|tenantId|tenantID|teamId|teamID|workspaceId|workspaceID|ghostOrg|role|roles|isAdmin|admin)\b/,
66379
+ requireAll: [/\b(?:supabase\b|\.from\s*\(\s*["'][^"']+["']\s*\))[\s\S]{0,700}\b(?:insert|upsert|update)\s*\(\s*(?:\{|\[?\s*\{)[\s\S]{0,700}\b(?:ownerId|creatorId|userId|orgId|tenantId|role|isAdmin)\b/i],
66380
+ message: "Client Supabase code appears to write user, tenant, owner, or role fields that should be enforced by RLS."
66381
+ });
63857
66382
  const supabaseClientOwnedAuthzField = defineRule({
63858
66383
  id: "supabase-client-owned-authz-field",
63859
66384
  title: "Client writes Supabase authorization field",
63860
66385
  severity: "error",
63861
66386
  recommendation: "Use RLS policies based on `auth.uid()` and server-owned membership rows; do not trust client-provided owner, org, or role columns.",
63862
- scan: scanByPattern({
63863
- shouldScan: (file) => isClientSourcePath(file.relativePath),
63864
- pattern: /\b(?:ownerId|ownerID|creatorId|creatorID|userId|userID|uid|providerId|providerID|orgId|orgID|tenantId|tenantID|teamId|teamID|workspaceId|workspaceID|ghostOrg|role|roles|isAdmin|admin)\b/,
63865
- requireAll: [/\b(?:supabase\b|\.from\s*\(\s*["'][^"']+["']\s*\))[\s\S]{0,700}\b(?:insert|upsert|update)\s*\(\s*(?:\{|\[?\s*\{)[\s\S]{0,700}\b(?:ownerId|creatorId|userId|orgId|tenantId|role|isAdmin)\b/i],
63866
- message: "Client Supabase code appears to write user, tenant, owner, or role fields that should be enforced by RLS."
63867
- })
66387
+ scan: (file) => {
66388
+ const findings = scanSupabaseClientOwnedAuthzField(file);
66389
+ if (findings.length === 0) return findings;
66390
+ return hasUseServerDirectiveInContent(file.content, file.relativePath) ? [] : findings;
66391
+ }
63868
66392
  });
63869
66393
  //#endregion
63870
66394
  //#region src/plugin/rules/security-scan/utils/is-supabase-migration-path.ts