oxlint-plugin-react-doctor 0.5.8-dev.05cafc6 → 0.5.8-dev.0b64af5

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 +306 -121
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -3,6 +3,48 @@ import * as fs from "node:fs";
3
3
  import { parseSync } from "oxc-parser";
4
4
  import { analyze } from "eslint-scope";
5
5
  import * as eslintVisitorKeys from "eslint-visitor-keys";
6
+ //#region src/plugin/utils/has-type-property.ts
7
+ const hasTypeProperty = (value) => Boolean(value && typeof value === "object" && "type" in value);
8
+ //#endregion
9
+ //#region src/plugin/utils/is-node-of-type.ts
10
+ const isNodeOfType = (node, type) => Boolean(hasTypeProperty(node) && node.type === type);
11
+ //#endregion
12
+ //#region src/plugin/utils/non-react-jsx-dialect.ts
13
+ const NON_REACT_JSX_DIALECT_PACKAGES = new Set([
14
+ "solid-js",
15
+ "solid-js/web",
16
+ "solid-js/store",
17
+ "solid-js/h",
18
+ "solid-js/html",
19
+ "@builder.io/qwik",
20
+ "@builder.io/qwik-city",
21
+ "@builder.io/qwik-react",
22
+ "voby",
23
+ "vidode"
24
+ ]);
25
+ const NON_REACT_JSX_DIALECT_PACKAGE_PREFIXES = ["solid-js", "@builder.io/qwik"];
26
+ const startsWithAny = (source, prefixes) => prefixes.some((prefix) => source === prefix || source.startsWith(`${prefix}/`));
27
+ const fileImportsNonReactJsxDialect = (program) => {
28
+ for (const statement of program.body) {
29
+ if (!isNodeOfType(statement, "ImportDeclaration")) continue;
30
+ const source = statement.source;
31
+ const value = source && typeof source.value === "string" ? source.value : null;
32
+ if (!value) continue;
33
+ if (NON_REACT_JSX_DIALECT_PACKAGES.has(value)) return true;
34
+ if (startsWithAny(value, NON_REACT_JSX_DIALECT_PACKAGE_PREFIXES)) return true;
35
+ }
36
+ return false;
37
+ };
38
+ const jsxAttributeIsNonReactDialectMarker = (openingNode) => {
39
+ for (const attribute of openingNode.attributes) {
40
+ if (!isNodeOfType(attribute, "JSXAttribute")) continue;
41
+ if (!isNodeOfType(attribute.name, "JSXIdentifier")) continue;
42
+ if (attribute.name.name === "classList") return true;
43
+ if (attribute.name.name.startsWith("class:") || attribute.name.name.startsWith("bind:")) return true;
44
+ }
45
+ return false;
46
+ };
47
+ //#endregion
6
48
  //#region src/plugin/utils/is-testlike-filename.ts
7
49
  const NON_PRODUCTION_PATH_SEGMENTS = [
8
50
  "/test/",
@@ -180,53 +222,10 @@ const isTestlikeFilename = (rawFilename) => {
180
222
  return false;
181
223
  };
182
224
  //#endregion
183
- //#region src/plugin/utils/has-type-property.ts
184
- const hasTypeProperty = (value) => Boolean(value && typeof value === "object" && "type" in value);
185
- //#endregion
186
- //#region src/plugin/utils/is-node-of-type.ts
187
- const isNodeOfType = (node, type) => Boolean(hasTypeProperty(node) && node.type === type);
188
- //#endregion
189
- //#region src/plugin/utils/non-react-jsx-dialect.ts
190
- const NON_REACT_JSX_DIALECT_PACKAGES = new Set([
191
- "solid-js",
192
- "solid-js/web",
193
- "solid-js/store",
194
- "solid-js/h",
195
- "solid-js/html",
196
- "@builder.io/qwik",
197
- "@builder.io/qwik-city",
198
- "@builder.io/qwik-react",
199
- "voby",
200
- "vidode"
201
- ]);
202
- const NON_REACT_JSX_DIALECT_PACKAGE_PREFIXES = ["solid-js", "@builder.io/qwik"];
203
- const startsWithAny = (source, prefixes) => prefixes.some((prefix) => source === prefix || source.startsWith(`${prefix}/`));
204
- const fileImportsNonReactJsxDialect = (program) => {
205
- for (const statement of program.body) {
206
- if (!isNodeOfType(statement, "ImportDeclaration")) continue;
207
- const source = statement.source;
208
- const value = source && typeof source.value === "string" ? source.value : null;
209
- if (!value) continue;
210
- if (NON_REACT_JSX_DIALECT_PACKAGES.has(value)) return true;
211
- if (startsWithAny(value, NON_REACT_JSX_DIALECT_PACKAGE_PREFIXES)) return true;
212
- }
213
- return false;
214
- };
215
- const jsxAttributeIsNonReactDialectMarker = (openingNode) => {
216
- for (const attribute of openingNode.attributes) {
217
- if (!isNodeOfType(attribute, "JSXAttribute")) continue;
218
- if (!isNodeOfType(attribute.name, "JSXIdentifier")) continue;
219
- if (attribute.name.name === "classList") return true;
220
- if (attribute.name.name.startsWith("class:") || attribute.name.name.startsWith("bind:")) return true;
221
- }
222
- return false;
223
- };
225
+ //#region src/plugin/utils/skip-non-production-files.ts
226
+ const skipNonProductionFiles = (create) => (context) => isTestlikeFilename(context.filename) ? {} : create(context);
224
227
  //#endregion
225
228
  //#region src/plugin/utils/define-rule.ts
226
- const wrapCreateForTestNoise = (create) => ((context) => {
227
- if (isTestlikeFilename(context.filename)) return {};
228
- return create(context);
229
- });
230
229
  const VISITOR_NODE_NAME_PATTERN = /^[A-Z]/;
231
230
  const wrapCreateForReactJsxOnly = (create) => ((context) => {
232
231
  const innerVisitors = create(context);
@@ -273,7 +272,7 @@ const defineRule = (rule) => {
273
272
  };
274
273
  const tags = rule.tags;
275
274
  let wrappedCreate = rule.create;
276
- if (tags?.includes("test-noise") && !tags?.includes("migration-hint")) wrappedCreate = wrapCreateForTestNoise(wrappedCreate);
275
+ if (tags?.includes("test-noise") && !tags?.includes("migration-hint")) wrappedCreate = skipNonProductionFiles(wrappedCreate);
277
276
  if (tags?.includes("react-jsx-only")) wrappedCreate = wrapCreateForReactJsxOnly(wrappedCreate);
278
277
  if (wrappedCreate === rule.create) return rule;
279
278
  return {
@@ -1176,6 +1175,20 @@ const getImportSourceForName = (contextNode, localIdentifierName) => {
1176
1175
  if (!lookup) return null;
1177
1176
  return lookup.get(localIdentifierName)?.source ?? null;
1178
1177
  };
1178
+ const getImportBindingForName = (contextNode, localIdentifierName) => {
1179
+ const info = getImportLookup(contextNode)?.get(localIdentifierName);
1180
+ if (!info) return null;
1181
+ if (info.isNamespace) return {
1182
+ source: info.source,
1183
+ exportedName: null,
1184
+ isNamespace: true
1185
+ };
1186
+ return {
1187
+ source: info.source,
1188
+ exportedName: info.isDefault ? "default" : info.imported,
1189
+ isNamespace: false
1190
+ };
1191
+ };
1179
1192
  //#endregion
1180
1193
  //#region src/plugin/utils/find-variable-initializer.ts
1181
1194
  const FUNCTION_LIKE_TYPES$1 = new Set([
@@ -1414,6 +1427,14 @@ const NEXTJS_NAVIGATION_FUNCTIONS = new Set([
1414
1427
  "forbidden",
1415
1428
  "unauthorized"
1416
1429
  ]);
1430
+ const CACHE_REVALIDATION_FUNCTION_NAMES = new Set([
1431
+ "revalidateTag",
1432
+ "revalidatePath",
1433
+ "expireTag",
1434
+ "expirePath",
1435
+ "unstable_expireTag",
1436
+ "unstable_expirePath"
1437
+ ]);
1417
1438
  const GOOGLE_FONTS_PATTERN = /fonts\.googleapis\.com/;
1418
1439
  const POLYFILL_SCRIPT_PATTERN = /polyfill\.io|polyfill\.min\.js|cdn\.polyfill/;
1419
1440
  const APP_DIRECTORY_PATTERN = /\/app\//;
@@ -4349,7 +4370,7 @@ const authTokenInWebStorage = defineRule({
4349
4370
  title: "Auth token in web storage",
4350
4371
  severity: "warn",
4351
4372
  recommendation: "Don't persist auth tokens (JWTs, access/refresh tokens, secrets) in `localStorage`/`sessionStorage`; they're readable by any XSS. Use an `HttpOnly` cookie set by the server.",
4352
- create: (context) => ({
4373
+ create: skipNonProductionFiles((context) => ({
4353
4374
  CallExpression(node) {
4354
4375
  const callee = node.callee;
4355
4376
  if (!isNodeOfType(callee, "MemberExpression") || callee.computed) return;
@@ -4374,7 +4395,7 @@ const authTokenInWebStorage = defineRule({
4374
4395
  message: MESSAGE$57
4375
4396
  });
4376
4397
  }
4377
- })
4398
+ }))
4378
4399
  });
4379
4400
  //#endregion
4380
4401
  //#region src/plugin/rules/a11y/autocomplete-valid.ts
@@ -6721,7 +6742,7 @@ const TRANSPARENT_WRAPPER_TYPES = new Set([
6721
6742
  "ParenthesizedExpression",
6722
6743
  "ChainExpression"
6723
6744
  ]);
6724
- const unwrapExpression$1 = (node) => {
6745
+ const unwrapExpression$2 = (node) => {
6725
6746
  let current = node;
6726
6747
  while (TRANSPARENT_WRAPPER_TYPES.has(current.type)) {
6727
6748
  const inner = current.expression;
@@ -6828,7 +6849,7 @@ const symbolHasStableHookOrigin = (symbol) => {
6828
6849
  if (!declarator || !isNodeOfType(declarator, "VariableDeclarator")) return false;
6829
6850
  const initializerRaw = declarator.init;
6830
6851
  if (!initializerRaw) return false;
6831
- const initializer = unwrapExpression$1(initializerRaw);
6852
+ const initializer = unwrapExpression$2(initializerRaw);
6832
6853
  if (symbol.kind === "const") {
6833
6854
  if (isNodeOfType(initializer, "Literal") && (initializer.value === null || typeof initializer.value === "number" || typeof initializer.value === "string" || typeof initializer.value === "boolean")) return true;
6834
6855
  if (isNodeOfType(initializer, "TemplateLiteral") && getStaticTemplateLiteralValue(initializer) !== null) return true;
@@ -6848,13 +6869,13 @@ const symbolHasStableHookOrigin = (symbol) => {
6848
6869
  return false;
6849
6870
  };
6850
6871
  const symbolHasUseEffectEventOrigin = (symbol) => {
6851
- const initializer = symbol.initializer ? unwrapExpression$1(symbol.initializer) : null;
6872
+ const initializer = symbol.initializer ? unwrapExpression$2(symbol.initializer) : null;
6852
6873
  if (!initializer || !isNodeOfType(initializer, "CallExpression")) return false;
6853
6874
  return getHookName(initializer.callee) === "useEffectEvent";
6854
6875
  };
6855
6876
  const getFunctionValueNode = (symbol) => {
6856
6877
  if (symbol.kind === "function" && isNodeOfType(symbol.declarationNode, "FunctionDeclaration")) return symbol.declarationNode;
6857
- const initializer = symbol.initializer ? unwrapExpression$1(symbol.initializer) : null;
6878
+ const initializer = symbol.initializer ? unwrapExpression$2(symbol.initializer) : null;
6858
6879
  if (initializer && (isNodeOfType(initializer, "FunctionExpression") || isNodeOfType(initializer, "ArrowFunctionExpression"))) return initializer;
6859
6880
  return null;
6860
6881
  };
@@ -6990,23 +7011,23 @@ const computeDepKey = (reference) => {
6990
7011
  return fullName;
6991
7012
  };
6992
7013
  const computeDeclaredDepKey = (entry) => {
6993
- const stripped = unwrapExpression$1(entry);
7014
+ const stripped = unwrapExpression$2(entry);
6994
7015
  if (isNodeOfType(stripped, "Identifier")) return stripped.name;
6995
7016
  if (isNodeOfType(stripped, "MemberExpression")) return stringifyMemberChain(stripped);
6996
7017
  return null;
6997
7018
  };
6998
7019
  const depsArrayContainsIdentifier = (depsArgument, identifierName) => {
6999
7020
  if (!depsArgument) return false;
7000
- const strippedDepsArgument = unwrapExpression$1(depsArgument);
7021
+ const strippedDepsArgument = unwrapExpression$2(depsArgument);
7001
7022
  if (!isNodeOfType(strippedDepsArgument, "ArrayExpression")) return false;
7002
7023
  return strippedDepsArgument.elements.some((element) => {
7003
7024
  if (!element) return false;
7004
- const strippedElement = unwrapExpression$1(element);
7025
+ const strippedElement = unwrapExpression$2(element);
7005
7026
  return isNodeOfType(strippedElement, "Identifier") && strippedElement.name === identifierName;
7006
7027
  });
7007
7028
  };
7008
7029
  const stringifyMemberChain = (node) => {
7009
- const stripped = unwrapExpression$1(node);
7030
+ const stripped = unwrapExpression$2(node);
7010
7031
  if (isNodeOfType(stripped, "Identifier")) return stripped.name;
7011
7032
  if (isNodeOfType(stripped, "ThisExpression")) return "this";
7012
7033
  if (isNodeOfType(stripped, "MemberExpression")) {
@@ -7048,13 +7069,13 @@ const hasBroaderDeclaredDependency = (declaredKey, declaredKeys) => {
7048
7069
  return false;
7049
7070
  };
7050
7071
  const getMemberRootIdentifier = (node) => {
7051
- const stripped = unwrapExpression$1(node);
7072
+ const stripped = unwrapExpression$2(node);
7052
7073
  if (isNodeOfType(stripped, "Identifier")) return stripped;
7053
7074
  if (isNodeOfType(stripped, "MemberExpression")) return getMemberRootIdentifier(stripped.object);
7054
7075
  return null;
7055
7076
  };
7056
7077
  const hasComputedMemberExpression = (node) => {
7057
- const stripped = unwrapExpression$1(node);
7078
+ const stripped = unwrapExpression$2(node);
7058
7079
  if (!isNodeOfType(stripped, "MemberExpression")) return false;
7059
7080
  if (stripped.computed) return true;
7060
7081
  return hasComputedMemberExpression(stripped.object);
@@ -7075,7 +7096,7 @@ const isRegExpLiteral = (node) => {
7075
7096
  };
7076
7097
  const isUnstableInitializer = (node) => {
7077
7098
  if (!node) return false;
7078
- const stripped = unwrapExpression$1(node);
7099
+ const stripped = unwrapExpression$2(node);
7079
7100
  if (isRegExpLiteral(stripped)) return true;
7080
7101
  if (isNodeOfType(stripped, "ConditionalExpression")) return isUnstableInitializer(stripped.consequent) || isUnstableInitializer(stripped.alternate);
7081
7102
  if (isNodeOfType(stripped, "LogicalExpression")) return isUnstableInitializer(stripped.left) || isUnstableInitializer(stripped.right);
@@ -7231,7 +7252,7 @@ const hasMemberCallForRoot = (node, rootName) => {
7231
7252
  const visit = (current) => {
7232
7253
  if (didFindMemberCall) return;
7233
7254
  if (isNodeOfType(current, "CallExpression")) {
7234
- if (getMemberRootIdentifier(unwrapExpression$1(current.callee))?.name === rootName) {
7255
+ if (getMemberRootIdentifier(unwrapExpression$2(current.callee))?.name === rootName) {
7235
7256
  didFindMemberCall = true;
7236
7257
  return;
7237
7258
  }
@@ -7294,7 +7315,7 @@ If the missing value is recreated every render, move it inside the hook or stabi
7294
7315
  return;
7295
7316
  }
7296
7317
  const depsArgumentRaw = node.arguments[depsArgumentIndex];
7297
- const callbackExpression = unwrapExpression$1(callbackArgument);
7318
+ const callbackExpression = unwrapExpression$2(callbackArgument);
7298
7319
  let callbackToAnalyze = null;
7299
7320
  const forcedCaptureKeys = /* @__PURE__ */ new Set();
7300
7321
  if (isNodeOfType(callbackExpression, "ArrowFunctionExpression") || isNodeOfType(callbackExpression, "FunctionExpression")) callbackToAnalyze = callbackExpression;
@@ -7302,7 +7323,7 @@ If the missing value is recreated every render, move it inside the hook or stabi
7302
7323
  const callbackSymbol = context.scopes.symbolFor(callbackExpression);
7303
7324
  const functionValueNode = callbackSymbol ? getFunctionValueNode(callbackSymbol) : null;
7304
7325
  if (functionValueNode) callbackToAnalyze = functionValueNode;
7305
- else if (callbackSymbol?.initializer && isNodeOfType(unwrapExpression$1(callbackSymbol.initializer), "CallExpression")) forcedCaptureKeys.add(callbackExpression.name);
7326
+ else if (callbackSymbol?.initializer && isNodeOfType(unwrapExpression$2(callbackSymbol.initializer), "CallExpression")) forcedCaptureKeys.add(callbackExpression.name);
7306
7327
  else if (depsArgumentRaw) {
7307
7328
  if (depsArrayContainsIdentifier(depsArgumentRaw, callbackExpression.name)) return;
7308
7329
  context.report({
@@ -7359,7 +7380,7 @@ If the missing value is recreated every render, move it inside the hook or stabi
7359
7380
  });
7360
7381
  return;
7361
7382
  }
7362
- const depsArgument = unwrapExpression$1(depsArgumentRaw);
7383
+ const depsArgument = unwrapExpression$2(depsArgumentRaw);
7363
7384
  if (isNodeOfType(depsArgument, "Literal") && depsArgument.value === null || isNodeOfType(depsArgument, "Identifier") && depsArgument.name === "undefined") {
7364
7385
  if (isAutoDependenciesHook(hookName)) return;
7365
7386
  if (HOOKS_REQUIRING_DEPS_ARRAY.has(hookName)) {
@@ -7400,11 +7421,11 @@ If the missing value is recreated every render, move it inside the hook or stabi
7400
7421
  for (const forcedCaptureKey of forcedCaptureKeys) captureKeys.add(forcedCaptureKey);
7401
7422
  const hasLiteralDepElement = depsArgument.elements.some((element) => {
7402
7423
  if (!element) return false;
7403
- return isLiteralOrEmptyTemplate(unwrapExpression$1(element));
7424
+ return isLiteralOrEmptyTemplate(unwrapExpression$2(element));
7404
7425
  });
7405
7426
  const hasNonStringLiteralDep = depsArgument.elements.some((element) => {
7406
7427
  if (!element) return false;
7407
- return isNonStringLiteral(unwrapExpression$1(element));
7428
+ return isNonStringLiteral(unwrapExpression$2(element));
7408
7429
  });
7409
7430
  if (hasNonStringLiteralDep) context.report({
7410
7431
  node: depsArgument,
@@ -7425,7 +7446,7 @@ If the missing value is recreated every render, move it inside the hook or stabi
7425
7446
  });
7426
7447
  continue;
7427
7448
  }
7428
- const stripped = unwrapExpression$1(elementNode);
7449
+ const stripped = unwrapExpression$2(elementNode);
7429
7450
  if (isLiteralOrEmptyTemplate(stripped)) continue;
7430
7451
  if (isNodeOfType(stripped, "Identifier")) {
7431
7452
  const depSymbol = context.scopes.symbolFor(stripped);
@@ -18946,7 +18967,7 @@ const noEval = defineRule({
18946
18967
  title: "eval() runs untrusted code strings",
18947
18968
  severity: "error",
18948
18969
  recommendation: "Use `JSON.parse` for data, or rewrite the code so it doesn't build and run code from strings.",
18949
- create: (context) => ({
18970
+ create: skipNonProductionFiles((context) => ({
18950
18971
  CallExpression(node) {
18951
18972
  if (isNodeOfType(node.callee, "Identifier") && node.callee.name === "eval") {
18952
18973
  context.report({
@@ -18966,7 +18987,7 @@ const noEval = defineRule({
18966
18987
  message: "new Function() is a code-injection vulnerability: it builds & runs code from a string."
18967
18988
  });
18968
18989
  }
18969
- })
18990
+ }))
18970
18991
  });
18971
18992
  //#endregion
18972
18993
  //#region src/plugin/rules/state-and-effects/no-event-handler.ts
@@ -27020,7 +27041,7 @@ const preferHtmlDialog = defineRule({
27020
27041
  });
27021
27042
  //#endregion
27022
27043
  //#region src/plugin/utils/function-returns-object-literal.ts
27023
- const unwrapExpression = (node) => {
27044
+ const unwrapExpression$1 = (node) => {
27024
27045
  let current = node;
27025
27046
  for (;;) {
27026
27047
  if ((current.type === "TSAsExpression" || current.type === "TSSatisfiesExpression" || current.type === "TSNonNullExpression") && "expression" in current && isAstNode(current.expression)) {
@@ -27033,7 +27054,7 @@ const unwrapExpression = (node) => {
27033
27054
  const doesFunctionReturnsObjectLiteral = (functionNode) => {
27034
27055
  if (functionNode.type === "ArrowFunctionExpression" && "body" in functionNode) {
27035
27056
  const body = functionNode.body;
27036
- if (body && body.type !== "BlockStatement") return unwrapExpression(body).type === "ObjectExpression";
27057
+ if (body && body.type !== "BlockStatement") return unwrapExpression$1(body).type === "ObjectExpression";
27037
27058
  }
27038
27059
  const body = functionNode.body;
27039
27060
  if (!body || body.type !== "BlockStatement") return false;
@@ -27041,7 +27062,7 @@ const doesFunctionReturnsObjectLiteral = (functionNode) => {
27041
27062
  const visit = (node) => {
27042
27063
  if (returnsObject) return;
27043
27064
  if (node.type === "ReturnStatement" && "argument" in node && node.argument != null) {
27044
- if (unwrapExpression(node.argument).type === "ObjectExpression") returnsObject = true;
27065
+ if (unwrapExpression$1(node.argument).type === "ObjectExpression") returnsObject = true;
27045
27066
  return;
27046
27067
  }
27047
27068
  const nodeRecord = node;
@@ -29473,6 +29494,19 @@ const REACT_NATIVE_TEXT_COMPONENTS = new Set([
29473
29494
  "H5",
29474
29495
  "H6"
29475
29496
  ]);
29497
+ const REACT_NATIVE_RAW_TEXT_HOST_COMPONENTS = new Set([
29498
+ "View",
29499
+ "ScrollView",
29500
+ "SafeAreaView",
29501
+ "KeyboardAvoidingView",
29502
+ "ImageBackground",
29503
+ "Modal",
29504
+ "Pressable",
29505
+ "TouchableOpacity",
29506
+ "TouchableHighlight",
29507
+ "TouchableWithoutFeedback",
29508
+ "TouchableNativeFeedback"
29509
+ ]);
29476
29510
  const REACT_NATIVE_TEXT_COMPONENT_KEYWORDS = new Set([
29477
29511
  "Text",
29478
29512
  "Title",
@@ -29485,7 +29519,11 @@ const REACT_NATIVE_TEXT_COMPONENT_KEYWORDS = new Set([
29485
29519
  "Description",
29486
29520
  "Body"
29487
29521
  ]);
29488
- const REACT_NATIVE_TEXT_TRANSPARENT_COMPONENTS = new Set(["fbt", "fbs"]);
29522
+ const REACT_NATIVE_TEXT_TRANSPARENT_COMPONENTS = new Set([
29523
+ "Fragment",
29524
+ "fbt",
29525
+ "fbs"
29526
+ ]);
29489
29527
  const DEPRECATED_RN_MODULE_REPLACEMENTS = new Map([
29490
29528
  ["AsyncStorage", "@react-native-async-storage/async-storage"],
29491
29529
  ["Picker", "@react-native-picker/picker"],
@@ -30443,23 +30481,29 @@ const jsxRootForwardsChildrenIntoText = (jsxRoot, bindings, isTextHandlingElemen
30443
30481
  return didForwardIntoText;
30444
30482
  };
30445
30483
  const isMeaningfulJsxChild = (child) => !isNodeOfType(child, "JSXText") || Boolean(child.value?.trim());
30446
- const jsxRootRendersChildrenOutsideText = (jsxRoot, bindings, isTextHandlingElement) => {
30447
- let didRenderOutsideText = false;
30484
+ const jsxRootForwardsChildren = (jsxRoot, bindings, isTextHandlingElement, countsAsForwardTarget) => {
30485
+ let didForward = false;
30448
30486
  walkAst(jsxRoot, (node) => {
30449
- if (didRenderOutsideText || isFunctionNode(node)) return false;
30487
+ if (didForward || isFunctionNode(node)) return false;
30450
30488
  if (!isNodeOfType(node, "JSXElement") && !isNodeOfType(node, "JSXFragment")) return;
30451
30489
  if (isNodeOfType(node, "JSXElement")) {
30452
30490
  const elementName = resolveJsxElementName(node.openingElement);
30453
30491
  if (elementName && isTextHandlingElement(elementName)) return false;
30454
- if (!(node.children ?? []).some(isMeaningfulJsxChild) && (node.openingElement.attributes ?? []).some((attribute) => isChildrenForwardingAttribute(attribute, bindings))) {
30455
- didRenderOutsideText = true;
30492
+ if (!(node.children ?? []).some(isMeaningfulJsxChild) && countsAsForwardTarget(node) && (node.openingElement.attributes ?? []).some((attribute) => isChildrenForwardingAttribute(attribute, bindings))) {
30493
+ didForward = true;
30456
30494
  return;
30457
30495
  }
30458
30496
  }
30459
- didRenderOutsideText = (node.children ?? []).some((child) => isChildrenForwardingJsxChild(child, bindings));
30497
+ if (countsAsForwardTarget(node) && (node.children ?? []).some((child) => isChildrenForwardingJsxChild(child, bindings))) didForward = true;
30460
30498
  });
30461
- return didRenderOutsideText;
30499
+ return didForward;
30462
30500
  };
30501
+ const jsxRootRendersChildrenOutsideText = (jsxRoot, bindings, isTextHandlingElement) => jsxRootForwardsChildren(jsxRoot, bindings, isTextHandlingElement, () => true);
30502
+ const jsxRootRendersChildrenIntoNonTextHost = (jsxRoot, bindings, isTextHandlingElement, isNonTextHostElement) => jsxRootForwardsChildren(jsxRoot, bindings, isTextHandlingElement, (node) => {
30503
+ if (!isNodeOfType(node, "JSXElement")) return false;
30504
+ const elementName = resolveJsxElementName(node.openingElement);
30505
+ return elementName !== null && isNonTextHostElement(elementName);
30506
+ });
30463
30507
  const resolveStyledFactoryBaseName = (definitionNode) => {
30464
30508
  let current = stripParenExpression(definitionNode);
30465
30509
  while (current) {
@@ -30496,49 +30540,71 @@ const resolveClassRenderFunction = (classNode) => {
30496
30540
  }
30497
30541
  return null;
30498
30542
  };
30499
- const recordWrapperFromDeclaration = (componentName, definitionNode, isTextHandlingElement, wrappers) => {
30500
- if (!componentName || !isReactComponentName(componentName)) return;
30501
- if (wrappers.has(componentName)) return;
30502
- if (!definitionNode) return;
30543
+ const classifyChildrenForwarding = (definitionNode, isTextHandlingElement, isNonTextHostElement) => {
30503
30544
  const unwrapped = unwrapComponentDefinition(definitionNode);
30504
30545
  const styledBaseName = resolveStyledFactoryBaseName(unwrapped);
30505
- if (styledBaseName && isTextHandlingElement(styledBaseName)) {
30506
- wrappers.add(componentName);
30507
- return;
30546
+ if (styledBaseName) {
30547
+ if (isTextHandlingElement(styledBaseName)) return "text";
30548
+ if (isNonTextHostElement(styledBaseName)) return "nonText";
30549
+ return "unknown";
30508
30550
  }
30509
30551
  const functionNode = resolveClassRenderFunction(unwrapped) ?? (isFunctionNode(unwrapped) ? unwrapped : null);
30510
- if (!functionNode) return;
30552
+ if (!functionNode) return "unknown";
30511
30553
  const bindings = resolveParamChildrenBindings(functionNode);
30512
30554
  collectChildrenAliases(functionNode, bindings);
30513
30555
  const jsxRoots = collectReturnedJsxRoots(functionNode);
30514
- if (jsxRoots.some((jsxRoot) => jsxRootRendersChildrenOutsideText(jsxRoot, bindings, isTextHandlingElement))) return;
30556
+ if (jsxRoots.some((jsxRoot) => jsxRootRendersChildrenIntoNonTextHost(jsxRoot, bindings, isTextHandlingElement, isNonTextHostElement))) return "nonText";
30557
+ if (jsxRoots.some((jsxRoot) => jsxRootRendersChildrenOutsideText(jsxRoot, bindings, isTextHandlingElement))) return "unknown";
30515
30558
  for (const jsxRoot of jsxRoots) {
30516
30559
  if (isNodeOfType(jsxRoot, "JSXElement")) {
30517
30560
  const rootName = resolveJsxElementName(jsxRoot.openingElement);
30518
- if (rootName && isTextHandlingElement(rootName)) {
30519
- wrappers.add(componentName);
30520
- return;
30521
- }
30522
- }
30523
- if (jsxRootForwardsChildrenIntoText(jsxRoot, bindings, isTextHandlingElement)) {
30524
- wrappers.add(componentName);
30525
- return;
30561
+ if (rootName && isTextHandlingElement(rootName)) return "text";
30526
30562
  }
30563
+ if (jsxRootForwardsChildrenIntoText(jsxRoot, bindings, isTextHandlingElement)) return "text";
30527
30564
  }
30565
+ return "unknown";
30566
+ };
30567
+ const recordWrapperFromDeclaration = (componentName, definitionNode, isTextHandlingElement, isNonTextHostElement, wrappers, nonTextWrappers) => {
30568
+ if (!componentName || !isReactComponentName(componentName)) return;
30569
+ if (wrappers.has(componentName)) return;
30570
+ if (!definitionNode) return;
30571
+ const kind = classifyChildrenForwarding(definitionNode, isTextHandlingElement, isNonTextHostElement);
30572
+ if (kind === "text") wrappers.add(componentName);
30573
+ else if (kind === "nonText") nonTextWrappers.add(componentName);
30528
30574
  };
30529
30575
  const MAX_TRANSITIVE_WRAPPER_PASSES = 3;
30530
- const collectTextWrapperComponents = (programNode, isTextHandlingRoot) => {
30576
+ const collectTextWrapperComponents = (programNode, isTextHandlingRoot, isNonTextHostRoot) => {
30531
30577
  const wrappers = /* @__PURE__ */ new Set();
30578
+ const nonTextWrappers = /* @__PURE__ */ new Set();
30532
30579
  const isTextHandlingElement = (elementName) => isTextHandlingRoot(elementName) || wrappers.has(elementName);
30580
+ const isNonTextHostElement = (elementName) => isNonTextHostRoot(elementName) || nonTextWrappers.has(elementName);
30581
+ const recordDeclaration = (componentName, definitionNode) => recordWrapperFromDeclaration(componentName, definitionNode, isTextHandlingElement, isNonTextHostElement, wrappers, nonTextWrappers);
30533
30582
  for (let pass = 0; pass < MAX_TRANSITIVE_WRAPPER_PASSES; pass += 1) {
30534
- const sizeBeforePass = wrappers.size;
30583
+ const wrappersSizeBeforePass = wrappers.size;
30584
+ const nonTextSizeBeforePass = nonTextWrappers.size;
30535
30585
  walkAst(programNode, (node) => {
30536
- if (isNodeOfType(node, "VariableDeclarator")) recordWrapperFromDeclaration(node.id && isNodeOfType(node.id, "Identifier") ? node.id.name : null, node.init, isTextHandlingElement, wrappers);
30537
- else if (isNodeOfType(node, "FunctionDeclaration") || isNodeOfType(node, "ClassDeclaration")) recordWrapperFromDeclaration(node.id && isNodeOfType(node.id, "Identifier") ? node.id.name : null, node, isTextHandlingElement, wrappers);
30586
+ if (isNodeOfType(node, "VariableDeclarator")) recordDeclaration(node.id && isNodeOfType(node.id, "Identifier") ? node.id.name : null, node.init ?? null);
30587
+ else if (isNodeOfType(node, "FunctionDeclaration") || isNodeOfType(node, "ClassDeclaration")) recordDeclaration(node.id && isNodeOfType(node.id, "Identifier") ? node.id.name : null, node);
30538
30588
  });
30539
- if (wrappers.size === sizeBeforePass) break;
30589
+ if (wrappers.size === wrappersSizeBeforePass && nonTextWrappers.size === nonTextSizeBeforePass) break;
30540
30590
  }
30541
- return wrappers;
30591
+ for (const wrapperName of wrappers) nonTextWrappers.delete(wrapperName);
30592
+ return {
30593
+ textWrappers: wrappers,
30594
+ nonTextWrappers
30595
+ };
30596
+ };
30597
+ //#endregion
30598
+ //#region src/plugin/rules/react-native/utils/resolve-imported-component-forwarding.ts
30599
+ const resolveImportedComponentForwarding = (contextNode, fromFilename, localName, isTextHandlingRoot, isNonTextHostRoot) => {
30600
+ const binding = getImportBindingForName(contextNode, localName);
30601
+ if (!binding || binding.isNamespace || binding.exportedName === null) return null;
30602
+ const resolvedNode = resolveCrossFileFunctionExport(fromFilename, binding.source, binding.exportedName);
30603
+ if (!resolvedNode) return null;
30604
+ const moduleProgram = findProgramRoot(resolvedNode);
30605
+ if (moduleProgram === null) return classifyChildrenForwarding(resolvedNode, isTextHandlingRoot, isNonTextHostRoot);
30606
+ const { textWrappers, nonTextWrappers } = collectTextWrapperComponents(moduleProgram, isTextHandlingRoot, isNonTextHostRoot);
30607
+ return classifyChildrenForwarding(resolvedNode, (elementName) => isTextHandlingRoot(elementName) || textWrappers.has(elementName), (elementName) => isNonTextHostRoot(elementName) || nonTextWrappers.has(elementName));
30542
30608
  };
30543
30609
  //#endregion
30544
30610
  //#region src/plugin/rules/react-native/utils/is-expo-ui-component-element.ts
@@ -30609,19 +30675,37 @@ const rnNoRawText = defineRule({
30609
30675
  recommendation: "Text outside a `<Text>` component crashes on React Native. Wrap it like `<Text>{value}</Text>`.",
30610
30676
  create: (context) => {
30611
30677
  let isDomComponentFile = false;
30612
- let autoDetectedWrappers = /* @__PURE__ */ new Set();
30678
+ let autoDetectedTextWrappers = /* @__PURE__ */ new Set();
30679
+ let autoDetectedNonTextWrappers = /* @__PURE__ */ new Set();
30680
+ const isNonTextHostName = (elementName) => !isReactComponentName(elementName) || REACT_NATIVE_RAW_TEXT_HOST_COMPONENTS.has(elementName);
30681
+ const isRawTextReportTarget = (elementName) => elementName !== null && (isNonTextHostName(elementName) || autoDetectedNonTextWrappers.has(elementName));
30682
+ const importedNonTextWrapperCache = /* @__PURE__ */ new Map();
30683
+ const isImportedNonTextWrapper = (elementName, contextNode) => {
30684
+ if (elementName === null || !isReactComponentName(elementName)) return false;
30685
+ const { filename } = context;
30686
+ if (filename === void 0) return false;
30687
+ const cached = importedNonTextWrapperCache.get(elementName);
30688
+ if (cached !== void 0) return cached;
30689
+ const isNonTextWrapper = resolveImportedComponentForwarding(contextNode, filename, elementName, isTextHandlingComponent, isNonTextHostName) === "nonText";
30690
+ importedNonTextWrapperCache.set(elementName, isNonTextWrapper);
30691
+ return isNonTextWrapper;
30692
+ };
30613
30693
  return {
30614
30694
  Program(programNode) {
30615
30695
  isDomComponentFile = hasDirective(programNode, "use dom");
30616
- autoDetectedWrappers = collectTextWrapperComponents(programNode, isTextHandlingComponent);
30696
+ const childrenForwarding = collectTextWrapperComponents(programNode, isTextHandlingComponent, isNonTextHostName);
30697
+ autoDetectedTextWrappers = childrenForwarding.textWrappers;
30698
+ autoDetectedNonTextWrappers = childrenForwarding.nonTextWrappers;
30617
30699
  },
30618
30700
  JSXElement(node) {
30619
30701
  if (isDomComponentFile) return;
30620
30702
  const elementName = resolveTextBoundaryName(node.openingElement);
30621
- if (elementName && (isTextHandlingComponent(elementName) || autoDetectedWrappers.has(elementName))) return;
30703
+ if (elementName && (isTextHandlingComponent(elementName) || autoDetectedTextWrappers.has(elementName))) return;
30622
30704
  if (isExpoUiComponentElement(node.openingElement, node, "ListItem")) return;
30623
30705
  if (isInsidePlatformOsWebBranch(node)) return;
30624
30706
  if (isTransparentTextWrapper(elementName) && isInsideTextHandlingComponent(node)) return;
30707
+ if (!(node.children ?? []).some(isRawTextContent)) return;
30708
+ if (!isRawTextReportTarget(elementName) && !isImportedNonTextWrapper(elementName, node)) return;
30625
30709
  for (const child of node.children ?? []) {
30626
30710
  if (!isRawTextContent(child)) continue;
30627
30711
  context.report({
@@ -35165,6 +35249,67 @@ const isAuthGuardName = (calleeName) => {
35165
35249
  return false;
35166
35250
  };
35167
35251
  //#endregion
35252
+ //#region src/plugin/utils/is-non-privileged-server-action.ts
35253
+ const NON_DATA_EFFECT_FUNCTION_NAMES = new Set([...CACHE_REVALIDATION_FUNCTION_NAMES, ...NEXTJS_NAVIGATION_FUNCTIONS]);
35254
+ const isCacheOrNavigationCall = (node) => isNodeOfType(node, "CallExpression") && isNodeOfType(node.callee, "Identifier") && NON_DATA_EFFECT_FUNCTION_NAMES.has(node.callee.name);
35255
+ const unwrapExpression = (node) => {
35256
+ let current = node;
35257
+ while (current) {
35258
+ if (isNodeOfType(current, "TSAsExpression") || isNodeOfType(current, "TSNonNullExpression") || isNodeOfType(current, "TSSatisfiesExpression") || isNodeOfType(current, "ChainExpression")) {
35259
+ current = current.expression;
35260
+ continue;
35261
+ }
35262
+ if (isNodeOfType(current, "SequenceExpression")) {
35263
+ current = current.expressions?.[current.expressions.length - 1];
35264
+ continue;
35265
+ }
35266
+ return current;
35267
+ }
35268
+ return null;
35269
+ };
35270
+ const isDataExposingValue = (node) => {
35271
+ const value = unwrapExpression(node);
35272
+ if (!value) return false;
35273
+ if (isCacheOrNavigationCall(value)) return false;
35274
+ return !isLiteralOnlyExpression(value);
35275
+ };
35276
+ const isLiteralOnlyExpression = (node) => {
35277
+ if (!node) return false;
35278
+ if (isNodeOfType(node, "Literal")) return true;
35279
+ if (isNodeOfType(node, "TemplateLiteral")) return (node.expressions ?? []).every(isLiteralOnlyExpression);
35280
+ if (isNodeOfType(node, "UnaryExpression")) return isLiteralOnlyExpression(node.argument);
35281
+ if (isNodeOfType(node, "ArrayExpression")) return (node.elements ?? []).every((element) => element === null || !isNodeOfType(element, "SpreadElement") && isLiteralOnlyExpression(element));
35282
+ if (isNodeOfType(node, "ObjectExpression")) return (node.properties ?? []).every((property) => isNodeOfType(property, "Property") && (!property.computed || isLiteralOnlyExpression(property.key)) && isLiteralOnlyExpression(property.value));
35283
+ return false;
35284
+ };
35285
+ const getReturnedOrThrownArgument = (node) => {
35286
+ if (isNodeOfType(node, "ReturnStatement")) return node.argument ?? null;
35287
+ if (isNodeOfType(node, "ThrowStatement")) return node.argument ?? null;
35288
+ return null;
35289
+ };
35290
+ const isDataExposingReturnOrThrow = (node) => isDataExposingValue(getReturnedOrThrownArgument(node));
35291
+ const isPrivilegedEffect = (node) => isNodeOfType(node, "CallExpression") || isNodeOfType(node, "TaggedTemplateExpression") || isNodeOfType(node, "NewExpression") || isNodeOfType(node, "AssignmentExpression") || isNodeOfType(node, "UpdateExpression") || isNodeOfType(node, "UnaryExpression") && node.operator === "delete" || isDataExposingReturnOrThrow(node);
35292
+ const isNonPrivilegedServerAction = (functionNode) => {
35293
+ const functionBody = functionNode.body;
35294
+ if (!functionBody) return false;
35295
+ if (!isNodeOfType(functionBody, "BlockStatement") && isDataExposingValue(functionBody)) return false;
35296
+ let hasNonDataEffectCall = false;
35297
+ let hasPrivilegedEffect = false;
35298
+ walkAst(functionBody, (child) => {
35299
+ if (hasPrivilegedEffect) return false;
35300
+ if (child !== functionBody && isFunctionLike$2(child)) return false;
35301
+ if (isCacheOrNavigationCall(child)) {
35302
+ hasNonDataEffectCall = true;
35303
+ return;
35304
+ }
35305
+ if (isPrivilegedEffect(child)) {
35306
+ hasPrivilegedEffect = true;
35307
+ return false;
35308
+ }
35309
+ });
35310
+ return hasNonDataEffectCall && !hasPrivilegedEffect;
35311
+ };
35312
+ //#endregion
35168
35313
  //#region src/plugin/rules/server/server-auth-actions.ts
35169
35314
  const isAsyncFunctionLikeNode = (node) => {
35170
35315
  if (!node) return false;
@@ -35239,6 +35384,7 @@ const getAuthScanRoots = (functionNode) => {
35239
35384
  const inspectServerAction = (candidate, fileHasUseServerDirective, allowedFunctionNames, context) => {
35240
35385
  if (!(fileHasUseServerDirective || hasUseServerDirective(candidate.functionNode))) return;
35241
35386
  if (containsAuthCheck(getAuthScanRoots(candidate.functionNode), allowedFunctionNames, GENERIC_AUTH_METHOD_NAMES)) return;
35387
+ if (isNonPrivilegedServerAction(candidate.functionNode)) return;
35242
35388
  context.report({
35243
35389
  node: candidate.reportNode,
35244
35390
  message: `Anyone can call server action "${candidate.displayName}" without logging in, since it has no auth check.`
@@ -35864,22 +36010,6 @@ const isSupabaseMigrationPath = (relativePath) => /(?:^|\/)supabase\/(?:migratio
35864
36010
  //#endregion
35865
36011
  //#region src/plugin/rules/security-scan/utils/is-sql-path.ts
35866
36012
  const isSqlPath = (relativePath) => relativePath.endsWith(".sql") || isSupabaseMigrationPath(relativePath);
35867
- const supabaseRlsPolicyRisk = defineRule({
35868
- id: "supabase-rls-policy-risk",
35869
- title: "Permissive Supabase RLS policy",
35870
- severity: "error",
35871
- recommendation: "Keep public-read policies explicit, but gate inserts, updates, deletes, and service-role bypasses behind `auth.uid()` plus trusted tenant membership.",
35872
- scan: scanByPattern({
35873
- shouldScan: (file) => isSqlPath(file.relativePath),
35874
- pattern: [
35875
- /disable\s+row\s+level\s+security/i,
35876
- /create\s+policy[\s\S]{0,700}auth\.role\(\)\s*=\s*["']service_role["']/i,
35877
- /create\s+policy[\s\S]{0,700}\bfor\s+(?:all|insert|update|delete)\b[\s\S]{0,500}\b(?:using|with\s+check)\s*\(\s*true\s*\)/i,
35878
- /create\s+policy(?:(?!\bfor\s+select\b)[\s\S]){0,700}\b(?:using|with\s+check)\s*\(\s*true\s*\)/i
35879
- ],
35880
- message: "Supabase policy SQL disables RLS, permits writes broadly, or references a service-role bypass."
35881
- })
35882
- });
35883
36013
  //#endregion
35884
36014
  //#region src/plugin/rules/security-scan/utils/sanitize-sql-for-scan.ts
35885
36015
  const DOLLAR_QUOTE_TAG_PATTERN = /^\$[A-Za-z_]?\w*\$/;
@@ -36056,6 +36186,60 @@ const sanitizeSqlForScan = (content) => {
36056
36186
  return characters.join("");
36057
36187
  };
36058
36188
  //#endregion
36189
+ //#region src/plugin/rules/security-scan/supabase-rls-policy-risk.ts
36190
+ const DISABLED_RLS_PATTERN = /disable\s+row\s+level\s+security/i;
36191
+ const SERVICE_ROLE_BODY_BYPASS_PATTERN = /auth\.role\(\)\s*=\s*["']service_role["']/i;
36192
+ const CREATE_POLICY_PATTERN = /create\s+policy/gi;
36193
+ const STATEMENT_END_PATTERN = /;|create\s+policy/i;
36194
+ const PERMISSIVE_TRUE_PATTERN = /\b(?:using|with\s+check)\s*\(\s*true\s*\)/i;
36195
+ const FOR_SELECT_PATTERN = /\bfor\s+select\b/i;
36196
+ const TO_CLAUSE_PATTERN = /\bto\s+([\s\S]+?)(?=\s+(?:using|with\s+check|as|for)\b|;|$)/i;
36197
+ const SERVER_ONLY_ROLES = new Set([
36198
+ "service_role",
36199
+ "postgres",
36200
+ "supabase_admin"
36201
+ ]);
36202
+ const isServerOnlyScoped = (statement) => {
36203
+ const toClause = TO_CLAUSE_PATTERN.exec(statement);
36204
+ if (toClause === null) return false;
36205
+ const roles = toClause[1].split(",").map((role) => role.trim().replace(/["'`]/g, "").toLowerCase()).filter(Boolean);
36206
+ return roles.length > 0 && roles.every((role) => SERVER_ONLY_ROLES.has(role));
36207
+ };
36208
+ const isRiskyPolicyStatement = (statement, rawStatement) => {
36209
+ if (SERVICE_ROLE_BODY_BYPASS_PATTERN.test(rawStatement)) return true;
36210
+ if (!PERMISSIVE_TRUE_PATTERN.test(statement)) return false;
36211
+ if (FOR_SELECT_PATTERN.test(statement)) return false;
36212
+ return !isServerOnlyScoped(statement);
36213
+ };
36214
+ const POLICY_RISK_MESSAGE = "Supabase policy SQL disables RLS, permits writes broadly, or references a service-role bypass.";
36215
+ const supabaseRlsPolicyRisk = defineRule({
36216
+ id: "supabase-rls-policy-risk",
36217
+ title: "Permissive Supabase RLS policy",
36218
+ severity: "error",
36219
+ recommendation: "Keep public-read policies explicit, but gate inserts, updates, deletes, and service-role bypasses behind `auth.uid()` plus trusted tenant membership.",
36220
+ scan: (file) => {
36221
+ if (!isSqlPath(file.relativePath)) return [];
36222
+ const content = sanitizeSqlForScan(file.content);
36223
+ let earliestRiskIndex = content.search(DISABLED_RLS_PATTERN);
36224
+ CREATE_POLICY_PATTERN.lastIndex = 0;
36225
+ for (let policyMatch = CREATE_POLICY_PATTERN.exec(content); policyMatch !== null; policyMatch = CREATE_POLICY_PATTERN.exec(content)) {
36226
+ const afterKeyword = policyMatch.index + policyMatch[0].length;
36227
+ const terminatorOffset = content.slice(afterKeyword).search(STATEMENT_END_PATTERN);
36228
+ const statementEnd = terminatorOffset < 0 ? content.length : afterKeyword + terminatorOffset;
36229
+ if (!isRiskyPolicyStatement(content.slice(policyMatch.index, statementEnd), file.content.slice(policyMatch.index, statementEnd))) continue;
36230
+ if (earliestRiskIndex < 0 || policyMatch.index < earliestRiskIndex) earliestRiskIndex = policyMatch.index;
36231
+ break;
36232
+ }
36233
+ if (earliestRiskIndex < 0) return [];
36234
+ const { line, column } = getLocationAtIndex(content, earliestRiskIndex);
36235
+ return [{
36236
+ message: POLICY_RISK_MESSAGE,
36237
+ line,
36238
+ column
36239
+ }];
36240
+ }
36241
+ });
36242
+ //#endregion
36059
36243
  //#region src/plugin/rules/security-scan/supabase-table-missing-rls.ts
36060
36244
  const CREATE_PUBLIC_TABLE_PATTERN = /create\s+(?:unlogged\s+)?table\s+(?:if\s+not\s+exists\s+)?(?!(?:auth|storage|realtime|vault|extensions|graphql|graphql_public|pgbouncer|net|supabase_functions|supabase_migrations|cron|pgsodium|pgmq|information_schema|pg_catalog|pg_temp|private|internal)\s*\.)(?:public\s*\.\s*)?["`]?([A-Za-z_][\w$]*)["`]?(?:\s*\(|\s+as\b)/gi;
36061
36245
  const enableRlsForTablePattern = (tableName) => new RegExp(`alter\\s+table\\s+(?:if\\s+exists\\s+)?(?:only\\s+)?(?:public\\s*\\.\\s*)?["\`]?${escapeRegExp(tableName)}["\`]?\\s+(?:force\\s+)?enable\\s+row\\s+level\\s+security`, "i");
@@ -42546,6 +42730,7 @@ const CROSS_FILE_RULE_IDS = new Set([
42546
42730
  "nextjs-missing-metadata",
42547
42731
  "nextjs-no-use-search-params-without-suspense",
42548
42732
  "no-mutating-reducer-state",
42733
+ "rn-no-raw-text",
42549
42734
  "rn-prefer-expo-image"
42550
42735
  ]);
42551
42736
  //#endregion
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "oxlint-plugin-react-doctor",
3
- "version": "0.5.8-dev.05cafc6",
3
+ "version": "0.5.8-dev.0b64af5",
4
4
  "description": "React Doctor rules for oxlint.",
5
5
  "keywords": [
6
6
  "accessibility",