oxlint-plugin-react-doctor 0.5.8-dev.c72b560 → 0.5.8-dev.e2393c4
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.
- package/dist/index.js +166 -50
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1176,6 +1176,20 @@ const getImportSourceForName = (contextNode, localIdentifierName) => {
|
|
|
1176
1176
|
if (!lookup) return null;
|
|
1177
1177
|
return lookup.get(localIdentifierName)?.source ?? null;
|
|
1178
1178
|
};
|
|
1179
|
+
const getImportBindingForName = (contextNode, localIdentifierName) => {
|
|
1180
|
+
const info = getImportLookup(contextNode)?.get(localIdentifierName);
|
|
1181
|
+
if (!info) return null;
|
|
1182
|
+
if (info.isNamespace) return {
|
|
1183
|
+
source: info.source,
|
|
1184
|
+
exportedName: null,
|
|
1185
|
+
isNamespace: true
|
|
1186
|
+
};
|
|
1187
|
+
return {
|
|
1188
|
+
source: info.source,
|
|
1189
|
+
exportedName: info.isDefault ? "default" : info.imported,
|
|
1190
|
+
isNamespace: false
|
|
1191
|
+
};
|
|
1192
|
+
};
|
|
1179
1193
|
//#endregion
|
|
1180
1194
|
//#region src/plugin/utils/find-variable-initializer.ts
|
|
1181
1195
|
const FUNCTION_LIKE_TYPES$1 = new Set([
|
|
@@ -29473,6 +29487,19 @@ const REACT_NATIVE_TEXT_COMPONENTS = new Set([
|
|
|
29473
29487
|
"H5",
|
|
29474
29488
|
"H6"
|
|
29475
29489
|
]);
|
|
29490
|
+
const REACT_NATIVE_RAW_TEXT_HOST_COMPONENTS = new Set([
|
|
29491
|
+
"View",
|
|
29492
|
+
"ScrollView",
|
|
29493
|
+
"SafeAreaView",
|
|
29494
|
+
"KeyboardAvoidingView",
|
|
29495
|
+
"ImageBackground",
|
|
29496
|
+
"Modal",
|
|
29497
|
+
"Pressable",
|
|
29498
|
+
"TouchableOpacity",
|
|
29499
|
+
"TouchableHighlight",
|
|
29500
|
+
"TouchableWithoutFeedback",
|
|
29501
|
+
"TouchableNativeFeedback"
|
|
29502
|
+
]);
|
|
29476
29503
|
const REACT_NATIVE_TEXT_COMPONENT_KEYWORDS = new Set([
|
|
29477
29504
|
"Text",
|
|
29478
29505
|
"Title",
|
|
@@ -29485,7 +29512,11 @@ const REACT_NATIVE_TEXT_COMPONENT_KEYWORDS = new Set([
|
|
|
29485
29512
|
"Description",
|
|
29486
29513
|
"Body"
|
|
29487
29514
|
]);
|
|
29488
|
-
const REACT_NATIVE_TEXT_TRANSPARENT_COMPONENTS = new Set([
|
|
29515
|
+
const REACT_NATIVE_TEXT_TRANSPARENT_COMPONENTS = new Set([
|
|
29516
|
+
"Fragment",
|
|
29517
|
+
"fbt",
|
|
29518
|
+
"fbs"
|
|
29519
|
+
]);
|
|
29489
29520
|
const DEPRECATED_RN_MODULE_REPLACEMENTS = new Map([
|
|
29490
29521
|
["AsyncStorage", "@react-native-async-storage/async-storage"],
|
|
29491
29522
|
["Picker", "@react-native-picker/picker"],
|
|
@@ -30443,23 +30474,29 @@ const jsxRootForwardsChildrenIntoText = (jsxRoot, bindings, isTextHandlingElemen
|
|
|
30443
30474
|
return didForwardIntoText;
|
|
30444
30475
|
};
|
|
30445
30476
|
const isMeaningfulJsxChild = (child) => !isNodeOfType(child, "JSXText") || Boolean(child.value?.trim());
|
|
30446
|
-
const
|
|
30447
|
-
let
|
|
30477
|
+
const jsxRootForwardsChildren = (jsxRoot, bindings, isTextHandlingElement, countsAsForwardTarget) => {
|
|
30478
|
+
let didForward = false;
|
|
30448
30479
|
walkAst(jsxRoot, (node) => {
|
|
30449
|
-
if (
|
|
30480
|
+
if (didForward || isFunctionNode(node)) return false;
|
|
30450
30481
|
if (!isNodeOfType(node, "JSXElement") && !isNodeOfType(node, "JSXFragment")) return;
|
|
30451
30482
|
if (isNodeOfType(node, "JSXElement")) {
|
|
30452
30483
|
const elementName = resolveJsxElementName(node.openingElement);
|
|
30453
30484
|
if (elementName && isTextHandlingElement(elementName)) return false;
|
|
30454
|
-
if (!(node.children ?? []).some(isMeaningfulJsxChild) && (node.openingElement.attributes ?? []).some((attribute) => isChildrenForwardingAttribute(attribute, bindings))) {
|
|
30455
|
-
|
|
30485
|
+
if (!(node.children ?? []).some(isMeaningfulJsxChild) && countsAsForwardTarget(node) && (node.openingElement.attributes ?? []).some((attribute) => isChildrenForwardingAttribute(attribute, bindings))) {
|
|
30486
|
+
didForward = true;
|
|
30456
30487
|
return;
|
|
30457
30488
|
}
|
|
30458
30489
|
}
|
|
30459
|
-
|
|
30490
|
+
if (countsAsForwardTarget(node) && (node.children ?? []).some((child) => isChildrenForwardingJsxChild(child, bindings))) didForward = true;
|
|
30460
30491
|
});
|
|
30461
|
-
return
|
|
30492
|
+
return didForward;
|
|
30462
30493
|
};
|
|
30494
|
+
const jsxRootRendersChildrenOutsideText = (jsxRoot, bindings, isTextHandlingElement) => jsxRootForwardsChildren(jsxRoot, bindings, isTextHandlingElement, () => true);
|
|
30495
|
+
const jsxRootRendersChildrenIntoNonTextHost = (jsxRoot, bindings, isTextHandlingElement, isNonTextHostElement) => jsxRootForwardsChildren(jsxRoot, bindings, isTextHandlingElement, (node) => {
|
|
30496
|
+
if (!isNodeOfType(node, "JSXElement")) return false;
|
|
30497
|
+
const elementName = resolveJsxElementName(node.openingElement);
|
|
30498
|
+
return elementName !== null && isNonTextHostElement(elementName);
|
|
30499
|
+
});
|
|
30463
30500
|
const resolveStyledFactoryBaseName = (definitionNode) => {
|
|
30464
30501
|
let current = stripParenExpression(definitionNode);
|
|
30465
30502
|
while (current) {
|
|
@@ -30496,49 +30533,71 @@ const resolveClassRenderFunction = (classNode) => {
|
|
|
30496
30533
|
}
|
|
30497
30534
|
return null;
|
|
30498
30535
|
};
|
|
30499
|
-
const
|
|
30500
|
-
if (!componentName || !isReactComponentName(componentName)) return;
|
|
30501
|
-
if (wrappers.has(componentName)) return;
|
|
30502
|
-
if (!definitionNode) return;
|
|
30536
|
+
const classifyChildrenForwarding = (definitionNode, isTextHandlingElement, isNonTextHostElement) => {
|
|
30503
30537
|
const unwrapped = unwrapComponentDefinition(definitionNode);
|
|
30504
30538
|
const styledBaseName = resolveStyledFactoryBaseName(unwrapped);
|
|
30505
|
-
if (styledBaseName
|
|
30506
|
-
|
|
30507
|
-
return;
|
|
30539
|
+
if (styledBaseName) {
|
|
30540
|
+
if (isTextHandlingElement(styledBaseName)) return "text";
|
|
30541
|
+
if (isNonTextHostElement(styledBaseName)) return "nonText";
|
|
30542
|
+
return "unknown";
|
|
30508
30543
|
}
|
|
30509
30544
|
const functionNode = resolveClassRenderFunction(unwrapped) ?? (isFunctionNode(unwrapped) ? unwrapped : null);
|
|
30510
|
-
if (!functionNode) return;
|
|
30545
|
+
if (!functionNode) return "unknown";
|
|
30511
30546
|
const bindings = resolveParamChildrenBindings(functionNode);
|
|
30512
30547
|
collectChildrenAliases(functionNode, bindings);
|
|
30513
30548
|
const jsxRoots = collectReturnedJsxRoots(functionNode);
|
|
30514
|
-
if (jsxRoots.some((jsxRoot) =>
|
|
30549
|
+
if (jsxRoots.some((jsxRoot) => jsxRootRendersChildrenIntoNonTextHost(jsxRoot, bindings, isTextHandlingElement, isNonTextHostElement))) return "nonText";
|
|
30550
|
+
if (jsxRoots.some((jsxRoot) => jsxRootRendersChildrenOutsideText(jsxRoot, bindings, isTextHandlingElement))) return "unknown";
|
|
30515
30551
|
for (const jsxRoot of jsxRoots) {
|
|
30516
30552
|
if (isNodeOfType(jsxRoot, "JSXElement")) {
|
|
30517
30553
|
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;
|
|
30554
|
+
if (rootName && isTextHandlingElement(rootName)) return "text";
|
|
30526
30555
|
}
|
|
30556
|
+
if (jsxRootForwardsChildrenIntoText(jsxRoot, bindings, isTextHandlingElement)) return "text";
|
|
30527
30557
|
}
|
|
30558
|
+
return "unknown";
|
|
30559
|
+
};
|
|
30560
|
+
const recordWrapperFromDeclaration = (componentName, definitionNode, isTextHandlingElement, isNonTextHostElement, wrappers, nonTextWrappers) => {
|
|
30561
|
+
if (!componentName || !isReactComponentName(componentName)) return;
|
|
30562
|
+
if (wrappers.has(componentName)) return;
|
|
30563
|
+
if (!definitionNode) return;
|
|
30564
|
+
const kind = classifyChildrenForwarding(definitionNode, isTextHandlingElement, isNonTextHostElement);
|
|
30565
|
+
if (kind === "text") wrappers.add(componentName);
|
|
30566
|
+
else if (kind === "nonText") nonTextWrappers.add(componentName);
|
|
30528
30567
|
};
|
|
30529
30568
|
const MAX_TRANSITIVE_WRAPPER_PASSES = 3;
|
|
30530
|
-
const collectTextWrapperComponents = (programNode, isTextHandlingRoot) => {
|
|
30569
|
+
const collectTextWrapperComponents = (programNode, isTextHandlingRoot, isNonTextHostRoot) => {
|
|
30531
30570
|
const wrappers = /* @__PURE__ */ new Set();
|
|
30571
|
+
const nonTextWrappers = /* @__PURE__ */ new Set();
|
|
30532
30572
|
const isTextHandlingElement = (elementName) => isTextHandlingRoot(elementName) || wrappers.has(elementName);
|
|
30573
|
+
const isNonTextHostElement = (elementName) => isNonTextHostRoot(elementName) || nonTextWrappers.has(elementName);
|
|
30574
|
+
const recordDeclaration = (componentName, definitionNode) => recordWrapperFromDeclaration(componentName, definitionNode, isTextHandlingElement, isNonTextHostElement, wrappers, nonTextWrappers);
|
|
30533
30575
|
for (let pass = 0; pass < MAX_TRANSITIVE_WRAPPER_PASSES; pass += 1) {
|
|
30534
|
-
const
|
|
30576
|
+
const wrappersSizeBeforePass = wrappers.size;
|
|
30577
|
+
const nonTextSizeBeforePass = nonTextWrappers.size;
|
|
30535
30578
|
walkAst(programNode, (node) => {
|
|
30536
|
-
if (isNodeOfType(node, "VariableDeclarator"))
|
|
30537
|
-
else if (isNodeOfType(node, "FunctionDeclaration") || isNodeOfType(node, "ClassDeclaration"))
|
|
30579
|
+
if (isNodeOfType(node, "VariableDeclarator")) recordDeclaration(node.id && isNodeOfType(node.id, "Identifier") ? node.id.name : null, node.init ?? null);
|
|
30580
|
+
else if (isNodeOfType(node, "FunctionDeclaration") || isNodeOfType(node, "ClassDeclaration")) recordDeclaration(node.id && isNodeOfType(node.id, "Identifier") ? node.id.name : null, node);
|
|
30538
30581
|
});
|
|
30539
|
-
if (wrappers.size ===
|
|
30582
|
+
if (wrappers.size === wrappersSizeBeforePass && nonTextWrappers.size === nonTextSizeBeforePass) break;
|
|
30540
30583
|
}
|
|
30541
|
-
|
|
30584
|
+
for (const wrapperName of wrappers) nonTextWrappers.delete(wrapperName);
|
|
30585
|
+
return {
|
|
30586
|
+
textWrappers: wrappers,
|
|
30587
|
+
nonTextWrappers
|
|
30588
|
+
};
|
|
30589
|
+
};
|
|
30590
|
+
//#endregion
|
|
30591
|
+
//#region src/plugin/rules/react-native/utils/resolve-imported-component-forwarding.ts
|
|
30592
|
+
const resolveImportedComponentForwarding = (contextNode, fromFilename, localName, isTextHandlingRoot, isNonTextHostRoot) => {
|
|
30593
|
+
const binding = getImportBindingForName(contextNode, localName);
|
|
30594
|
+
if (!binding || binding.isNamespace || binding.exportedName === null) return null;
|
|
30595
|
+
const resolvedNode = resolveCrossFileFunctionExport(fromFilename, binding.source, binding.exportedName);
|
|
30596
|
+
if (!resolvedNode) return null;
|
|
30597
|
+
const moduleProgram = findProgramRoot(resolvedNode);
|
|
30598
|
+
if (moduleProgram === null) return classifyChildrenForwarding(resolvedNode, isTextHandlingRoot, isNonTextHostRoot);
|
|
30599
|
+
const { textWrappers, nonTextWrappers } = collectTextWrapperComponents(moduleProgram, isTextHandlingRoot, isNonTextHostRoot);
|
|
30600
|
+
return classifyChildrenForwarding(resolvedNode, (elementName) => isTextHandlingRoot(elementName) || textWrappers.has(elementName), (elementName) => isNonTextHostRoot(elementName) || nonTextWrappers.has(elementName));
|
|
30542
30601
|
};
|
|
30543
30602
|
//#endregion
|
|
30544
30603
|
//#region src/plugin/rules/react-native/utils/is-expo-ui-component-element.ts
|
|
@@ -30609,19 +30668,37 @@ const rnNoRawText = defineRule({
|
|
|
30609
30668
|
recommendation: "Text outside a `<Text>` component crashes on React Native. Wrap it like `<Text>{value}</Text>`.",
|
|
30610
30669
|
create: (context) => {
|
|
30611
30670
|
let isDomComponentFile = false;
|
|
30612
|
-
let
|
|
30671
|
+
let autoDetectedTextWrappers = /* @__PURE__ */ new Set();
|
|
30672
|
+
let autoDetectedNonTextWrappers = /* @__PURE__ */ new Set();
|
|
30673
|
+
const isNonTextHostName = (elementName) => !isReactComponentName(elementName) || REACT_NATIVE_RAW_TEXT_HOST_COMPONENTS.has(elementName);
|
|
30674
|
+
const isRawTextReportTarget = (elementName) => elementName !== null && (isNonTextHostName(elementName) || autoDetectedNonTextWrappers.has(elementName));
|
|
30675
|
+
const importedNonTextWrapperCache = /* @__PURE__ */ new Map();
|
|
30676
|
+
const isImportedNonTextWrapper = (elementName, contextNode) => {
|
|
30677
|
+
if (elementName === null || !isReactComponentName(elementName)) return false;
|
|
30678
|
+
const { filename } = context;
|
|
30679
|
+
if (filename === void 0) return false;
|
|
30680
|
+
const cached = importedNonTextWrapperCache.get(elementName);
|
|
30681
|
+
if (cached !== void 0) return cached;
|
|
30682
|
+
const isNonTextWrapper = resolveImportedComponentForwarding(contextNode, filename, elementName, isTextHandlingComponent, isNonTextHostName) === "nonText";
|
|
30683
|
+
importedNonTextWrapperCache.set(elementName, isNonTextWrapper);
|
|
30684
|
+
return isNonTextWrapper;
|
|
30685
|
+
};
|
|
30613
30686
|
return {
|
|
30614
30687
|
Program(programNode) {
|
|
30615
30688
|
isDomComponentFile = hasDirective(programNode, "use dom");
|
|
30616
|
-
|
|
30689
|
+
const childrenForwarding = collectTextWrapperComponents(programNode, isTextHandlingComponent, isNonTextHostName);
|
|
30690
|
+
autoDetectedTextWrappers = childrenForwarding.textWrappers;
|
|
30691
|
+
autoDetectedNonTextWrappers = childrenForwarding.nonTextWrappers;
|
|
30617
30692
|
},
|
|
30618
30693
|
JSXElement(node) {
|
|
30619
30694
|
if (isDomComponentFile) return;
|
|
30620
30695
|
const elementName = resolveTextBoundaryName(node.openingElement);
|
|
30621
|
-
if (elementName && (isTextHandlingComponent(elementName) ||
|
|
30696
|
+
if (elementName && (isTextHandlingComponent(elementName) || autoDetectedTextWrappers.has(elementName))) return;
|
|
30622
30697
|
if (isExpoUiComponentElement(node.openingElement, node, "ListItem")) return;
|
|
30623
30698
|
if (isInsidePlatformOsWebBranch(node)) return;
|
|
30624
30699
|
if (isTransparentTextWrapper(elementName) && isInsideTextHandlingComponent(node)) return;
|
|
30700
|
+
if (!(node.children ?? []).some(isRawTextContent)) return;
|
|
30701
|
+
if (!isRawTextReportTarget(elementName) && !isImportedNonTextWrapper(elementName, node)) return;
|
|
30625
30702
|
for (const child of node.children ?? []) {
|
|
30626
30703
|
if (!isRawTextContent(child)) continue;
|
|
30627
30704
|
context.report({
|
|
@@ -35864,22 +35941,6 @@ const isSupabaseMigrationPath = (relativePath) => /(?:^|\/)supabase\/(?:migratio
|
|
|
35864
35941
|
//#endregion
|
|
35865
35942
|
//#region src/plugin/rules/security-scan/utils/is-sql-path.ts
|
|
35866
35943
|
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
35944
|
//#endregion
|
|
35884
35945
|
//#region src/plugin/rules/security-scan/utils/sanitize-sql-for-scan.ts
|
|
35885
35946
|
const DOLLAR_QUOTE_TAG_PATTERN = /^\$[A-Za-z_]?\w*\$/;
|
|
@@ -36056,6 +36117,60 @@ const sanitizeSqlForScan = (content) => {
|
|
|
36056
36117
|
return characters.join("");
|
|
36057
36118
|
};
|
|
36058
36119
|
//#endregion
|
|
36120
|
+
//#region src/plugin/rules/security-scan/supabase-rls-policy-risk.ts
|
|
36121
|
+
const DISABLED_RLS_PATTERN = /disable\s+row\s+level\s+security/i;
|
|
36122
|
+
const SERVICE_ROLE_BODY_BYPASS_PATTERN = /auth\.role\(\)\s*=\s*["']service_role["']/i;
|
|
36123
|
+
const CREATE_POLICY_PATTERN = /create\s+policy/gi;
|
|
36124
|
+
const STATEMENT_END_PATTERN = /;|create\s+policy/i;
|
|
36125
|
+
const PERMISSIVE_TRUE_PATTERN = /\b(?:using|with\s+check)\s*\(\s*true\s*\)/i;
|
|
36126
|
+
const FOR_SELECT_PATTERN = /\bfor\s+select\b/i;
|
|
36127
|
+
const TO_CLAUSE_PATTERN = /\bto\s+([\s\S]+?)(?=\s+(?:using|with\s+check|as|for)\b|;|$)/i;
|
|
36128
|
+
const SERVER_ONLY_ROLES = new Set([
|
|
36129
|
+
"service_role",
|
|
36130
|
+
"postgres",
|
|
36131
|
+
"supabase_admin"
|
|
36132
|
+
]);
|
|
36133
|
+
const isServerOnlyScoped = (statement) => {
|
|
36134
|
+
const toClause = TO_CLAUSE_PATTERN.exec(statement);
|
|
36135
|
+
if (toClause === null) return false;
|
|
36136
|
+
const roles = toClause[1].split(",").map((role) => role.trim().replace(/["'`]/g, "").toLowerCase()).filter(Boolean);
|
|
36137
|
+
return roles.length > 0 && roles.every((role) => SERVER_ONLY_ROLES.has(role));
|
|
36138
|
+
};
|
|
36139
|
+
const isRiskyPolicyStatement = (statement, rawStatement) => {
|
|
36140
|
+
if (SERVICE_ROLE_BODY_BYPASS_PATTERN.test(rawStatement)) return true;
|
|
36141
|
+
if (!PERMISSIVE_TRUE_PATTERN.test(statement)) return false;
|
|
36142
|
+
if (FOR_SELECT_PATTERN.test(statement)) return false;
|
|
36143
|
+
return !isServerOnlyScoped(statement);
|
|
36144
|
+
};
|
|
36145
|
+
const POLICY_RISK_MESSAGE = "Supabase policy SQL disables RLS, permits writes broadly, or references a service-role bypass.";
|
|
36146
|
+
const supabaseRlsPolicyRisk = defineRule({
|
|
36147
|
+
id: "supabase-rls-policy-risk",
|
|
36148
|
+
title: "Permissive Supabase RLS policy",
|
|
36149
|
+
severity: "error",
|
|
36150
|
+
recommendation: "Keep public-read policies explicit, but gate inserts, updates, deletes, and service-role bypasses behind `auth.uid()` plus trusted tenant membership.",
|
|
36151
|
+
scan: (file) => {
|
|
36152
|
+
if (!isSqlPath(file.relativePath)) return [];
|
|
36153
|
+
const content = sanitizeSqlForScan(file.content);
|
|
36154
|
+
let earliestRiskIndex = content.search(DISABLED_RLS_PATTERN);
|
|
36155
|
+
CREATE_POLICY_PATTERN.lastIndex = 0;
|
|
36156
|
+
for (let policyMatch = CREATE_POLICY_PATTERN.exec(content); policyMatch !== null; policyMatch = CREATE_POLICY_PATTERN.exec(content)) {
|
|
36157
|
+
const afterKeyword = policyMatch.index + policyMatch[0].length;
|
|
36158
|
+
const terminatorOffset = content.slice(afterKeyword).search(STATEMENT_END_PATTERN);
|
|
36159
|
+
const statementEnd = terminatorOffset < 0 ? content.length : afterKeyword + terminatorOffset;
|
|
36160
|
+
if (!isRiskyPolicyStatement(content.slice(policyMatch.index, statementEnd), file.content.slice(policyMatch.index, statementEnd))) continue;
|
|
36161
|
+
if (earliestRiskIndex < 0 || policyMatch.index < earliestRiskIndex) earliestRiskIndex = policyMatch.index;
|
|
36162
|
+
break;
|
|
36163
|
+
}
|
|
36164
|
+
if (earliestRiskIndex < 0) return [];
|
|
36165
|
+
const { line, column } = getLocationAtIndex(content, earliestRiskIndex);
|
|
36166
|
+
return [{
|
|
36167
|
+
message: POLICY_RISK_MESSAGE,
|
|
36168
|
+
line,
|
|
36169
|
+
column
|
|
36170
|
+
}];
|
|
36171
|
+
}
|
|
36172
|
+
});
|
|
36173
|
+
//#endregion
|
|
36059
36174
|
//#region src/plugin/rules/security-scan/supabase-table-missing-rls.ts
|
|
36060
36175
|
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
36176
|
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 +42661,7 @@ const CROSS_FILE_RULE_IDS = new Set([
|
|
|
42546
42661
|
"nextjs-missing-metadata",
|
|
42547
42662
|
"nextjs-no-use-search-params-without-suspense",
|
|
42548
42663
|
"no-mutating-reducer-state",
|
|
42664
|
+
"rn-no-raw-text",
|
|
42549
42665
|
"rn-prefer-expo-image"
|
|
42550
42666
|
]);
|
|
42551
42667
|
//#endregion
|