oxlint-plugin-react-doctor 0.5.6-dev.f45cb29 → 0.5.7-dev.0b4f4f4
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.d.ts +130 -2
- package/dist/index.js +512 -244
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -890,24 +890,64 @@ const advancedEventHandlerRefs = defineRule({
|
|
|
890
890
|
});
|
|
891
891
|
//#endregion
|
|
892
892
|
//#region src/plugin/rules/security-scan/utils/strip-comments-preserving-positions.ts
|
|
893
|
-
const
|
|
893
|
+
const WHITESPACE_PATTERN = /\s/;
|
|
894
|
+
const quotedLiteralHasWhitespace = (content, openQuoteIndex, delimiter) => {
|
|
895
|
+
for (let cursor = openQuoteIndex + 1; cursor < content.length; cursor += 1) {
|
|
896
|
+
const character = content[cursor];
|
|
897
|
+
if (character === "\\") {
|
|
898
|
+
cursor += 1;
|
|
899
|
+
continue;
|
|
900
|
+
}
|
|
901
|
+
if (character === delimiter) return false;
|
|
902
|
+
if (WHITESPACE_PATTERN.test(character)) return true;
|
|
903
|
+
}
|
|
904
|
+
return false;
|
|
905
|
+
};
|
|
906
|
+
const blankNonCodePreservingPositions = (content, blankStringContents) => {
|
|
894
907
|
const characters = content.split("");
|
|
895
908
|
let stringDelimiter = null;
|
|
909
|
+
let isBlankingString = false;
|
|
910
|
+
const templateExpressionDepths = [];
|
|
896
911
|
let index = 0;
|
|
912
|
+
const blankUnlessNewline = (offset) => {
|
|
913
|
+
if (offset < content.length && content[offset] !== "\n") characters[offset] = " ";
|
|
914
|
+
};
|
|
897
915
|
while (index < content.length) {
|
|
898
916
|
const character = content[index];
|
|
899
917
|
const nextCharacter = content[index + 1];
|
|
900
918
|
if (stringDelimiter !== null) {
|
|
901
919
|
if (character === "\\") {
|
|
920
|
+
if (isBlankingString) {
|
|
921
|
+
blankUnlessNewline(index);
|
|
922
|
+
blankUnlessNewline(index + 1);
|
|
923
|
+
}
|
|
902
924
|
index += 2;
|
|
903
925
|
continue;
|
|
904
926
|
}
|
|
905
|
-
if (character === stringDelimiter)
|
|
927
|
+
if (character === stringDelimiter) {
|
|
928
|
+
stringDelimiter = null;
|
|
929
|
+
index += 1;
|
|
930
|
+
continue;
|
|
931
|
+
}
|
|
932
|
+
if (blankStringContents && stringDelimiter === "`" && character === "$" && nextCharacter === "{") {
|
|
933
|
+
templateExpressionDepths.push(0);
|
|
934
|
+
stringDelimiter = null;
|
|
935
|
+
index += 2;
|
|
936
|
+
continue;
|
|
937
|
+
}
|
|
938
|
+
if (isBlankingString) blankUnlessNewline(index);
|
|
906
939
|
index += 1;
|
|
907
940
|
continue;
|
|
908
941
|
}
|
|
909
|
-
if (character === "\"" || character === "'"
|
|
942
|
+
if (character === "\"" || character === "'") {
|
|
910
943
|
stringDelimiter = character;
|
|
944
|
+
isBlankingString = blankStringContents && quotedLiteralHasWhitespace(content, index, character);
|
|
945
|
+
index += 1;
|
|
946
|
+
continue;
|
|
947
|
+
}
|
|
948
|
+
if (character === "`") {
|
|
949
|
+
stringDelimiter = "`";
|
|
950
|
+
isBlankingString = blankStringContents;
|
|
911
951
|
index += 1;
|
|
912
952
|
continue;
|
|
913
953
|
}
|
|
@@ -926,29 +966,42 @@ const stripCommentsPreservingPositions = (content) => {
|
|
|
926
966
|
index += 2;
|
|
927
967
|
break;
|
|
928
968
|
}
|
|
929
|
-
|
|
969
|
+
blankUnlessNewline(index);
|
|
930
970
|
index += 1;
|
|
931
971
|
}
|
|
932
972
|
continue;
|
|
933
973
|
}
|
|
974
|
+
if (templateExpressionDepths.length > 0) {
|
|
975
|
+
const innermost = templateExpressionDepths.length - 1;
|
|
976
|
+
if (character === "{") templateExpressionDepths[innermost] += 1;
|
|
977
|
+
else if (character === "}") if (templateExpressionDepths[innermost] === 0) {
|
|
978
|
+
templateExpressionDepths.pop();
|
|
979
|
+
stringDelimiter = "`";
|
|
980
|
+
isBlankingString = blankStringContents;
|
|
981
|
+
} else templateExpressionDepths[innermost] -= 1;
|
|
982
|
+
}
|
|
934
983
|
index += 1;
|
|
935
984
|
}
|
|
936
985
|
return characters.join("");
|
|
937
986
|
};
|
|
987
|
+
const stripCommentsPreservingPositions = (content) => blankNonCodePreservingPositions(content, false);
|
|
988
|
+
const stripCommentsAndStringLiteralsPreservingPositions = (content) => blankNonCodePreservingPositions(content, true);
|
|
938
989
|
//#endregion
|
|
939
990
|
//#region src/plugin/rules/security-scan/utils/scan-by-pattern.ts
|
|
940
991
|
const strippedContentCache = /* @__PURE__ */ new WeakMap();
|
|
941
|
-
const
|
|
992
|
+
const stringStrippedContentCache = /* @__PURE__ */ new WeakMap();
|
|
993
|
+
const getScannableContent = (file, ignoreStringLiterals = false) => {
|
|
942
994
|
if (!SOURCE_FILE_PATTERN.test(file.relativePath)) return file.content;
|
|
943
|
-
const
|
|
995
|
+
const cache = ignoreStringLiterals ? stringStrippedContentCache : strippedContentCache;
|
|
996
|
+
const cachedContent = cache.get(file);
|
|
944
997
|
if (cachedContent !== void 0) return cachedContent;
|
|
945
|
-
const strippedContent = stripCommentsPreservingPositions(file.content);
|
|
946
|
-
|
|
998
|
+
const strippedContent = ignoreStringLiterals ? stripCommentsAndStringLiteralsPreservingPositions(file.content) : stripCommentsPreservingPositions(file.content);
|
|
999
|
+
cache.set(file, strippedContent);
|
|
947
1000
|
return strippedContent;
|
|
948
1001
|
};
|
|
949
|
-
const scanByPattern = ({ shouldScan, pattern, requireAll, suppressWhen, message }) => (file) => {
|
|
1002
|
+
const scanByPattern = ({ shouldScan, pattern, requireAll, suppressWhen, ignoreStringLiterals, message }) => (file) => {
|
|
950
1003
|
if (!shouldScan(file)) return [];
|
|
951
|
-
const content = getScannableContent(file);
|
|
1004
|
+
const content = getScannableContent(file, ignoreStringLiterals);
|
|
952
1005
|
if (requireAll !== void 0 && !requireAll.every((gate) => gate.test(content))) return [];
|
|
953
1006
|
const matchedPattern = (pattern instanceof RegExp ? [pattern] : pattern).find((candidate) => candidate.test(content));
|
|
954
1007
|
if (matchedPattern === void 0) return [];
|
|
@@ -973,6 +1026,7 @@ const agentToolCapabilityRisk = defineRule({
|
|
|
973
1026
|
shouldScan: (file) => isProductionSourcePath(file.relativePath) && AGENT_TOOL_CONTEXT_PATH_PATTERN.test(file.relativePath),
|
|
974
1027
|
pattern: AGENT_TOOL_DEFINITION_PATTERN,
|
|
975
1028
|
requireAll: [AGENT_TOOL_DANGEROUS_CAPABILITY_PATTERN],
|
|
1029
|
+
ignoreStringLiterals: true,
|
|
976
1030
|
message: "An agent-callable tool appears to expose network, filesystem, shell, or code-execution capability."
|
|
977
1031
|
})
|
|
978
1032
|
});
|
|
@@ -1861,7 +1915,7 @@ const anchorAmbiguousText = defineRule({
|
|
|
1861
1915
|
});
|
|
1862
1916
|
//#endregion
|
|
1863
1917
|
//#region src/plugin/rules/a11y/anchor-has-content.ts
|
|
1864
|
-
const MESSAGE$
|
|
1918
|
+
const MESSAGE$59 = "Blind users can't follow this link because screen readers announce nothing, so add visible text, `aria-label`, or `aria-labelledby`.";
|
|
1865
1919
|
const anchorHasContent = defineRule({
|
|
1866
1920
|
id: "anchor-has-content",
|
|
1867
1921
|
title: "Anchor has no content",
|
|
@@ -1877,7 +1931,7 @@ const anchorHasContent = defineRule({
|
|
|
1877
1931
|
for (const attribute of ["title", "aria-label"]) if (hasJsxPropIgnoreCase(opening.attributes, attribute)) return;
|
|
1878
1932
|
context.report({
|
|
1879
1933
|
node: opening.name,
|
|
1880
|
-
message: MESSAGE$
|
|
1934
|
+
message: MESSAGE$59
|
|
1881
1935
|
});
|
|
1882
1936
|
} })
|
|
1883
1937
|
});
|
|
@@ -2271,7 +2325,7 @@ const parseJsxValue = (value) => {
|
|
|
2271
2325
|
};
|
|
2272
2326
|
//#endregion
|
|
2273
2327
|
//#region src/plugin/rules/a11y/aria-activedescendant-has-tabindex.ts
|
|
2274
|
-
const MESSAGE$
|
|
2328
|
+
const MESSAGE$58 = "Keyboard users can't focus this element with `aria-activedescendant` because it isn't tabbable, so add `tabIndex={0}`.";
|
|
2275
2329
|
const ariaActivedescendantHasTabindex = defineRule({
|
|
2276
2330
|
id: "aria-activedescendant-has-tabindex",
|
|
2277
2331
|
title: "aria-activedescendant missing tabindex",
|
|
@@ -2289,14 +2343,14 @@ const ariaActivedescendantHasTabindex = defineRule({
|
|
|
2289
2343
|
if (tabIndexValue === null || tabIndexValue >= -1) return;
|
|
2290
2344
|
context.report({
|
|
2291
2345
|
node: node.name,
|
|
2292
|
-
message: MESSAGE$
|
|
2346
|
+
message: MESSAGE$58
|
|
2293
2347
|
});
|
|
2294
2348
|
return;
|
|
2295
2349
|
}
|
|
2296
2350
|
if (isInteractiveElement(tag, node)) return;
|
|
2297
2351
|
context.report({
|
|
2298
2352
|
node: node.name,
|
|
2299
|
-
message: MESSAGE$
|
|
2353
|
+
message: MESSAGE$58
|
|
2300
2354
|
});
|
|
2301
2355
|
} })
|
|
2302
2356
|
});
|
|
@@ -2965,7 +3019,7 @@ const ABSTRACT_ROLES = new Set([
|
|
|
2965
3019
|
"widget",
|
|
2966
3020
|
"window"
|
|
2967
3021
|
]);
|
|
2968
|
-
const PRESENTATION_ROLES$
|
|
3022
|
+
const PRESENTATION_ROLES$1 = new Set(["presentation", "none"]);
|
|
2969
3023
|
//#endregion
|
|
2970
3024
|
//#region src/plugin/rules/a11y/aria-role.ts
|
|
2971
3025
|
const buildBaseMessage = (suffix) => `This \`role\` is not a valid ARIA role, so assistive tech cannot expose it correctly. Use a real, non-abstract role.${suffix}`;
|
|
@@ -3085,7 +3139,7 @@ const artifactBaasAuthoritySurface = defineRule({
|
|
|
3085
3139
|
scan: scanByPattern({
|
|
3086
3140
|
shouldScan: (file) => isBrowserArtifactPath(file.relativePath, file.isGeneratedBundle),
|
|
3087
3141
|
pattern: /\b(?:collection\s*\(\s*["'](?:boosts|sessions|sessions_admin|users|orgs|candidateJobs|conversations|documents|profiles)|from\s*\(\s*["'](?:users|profiles|documents|organizations|memberships)|creatorID|creatorId|providerId|ghostOrg|ownerId|orgId|tenantId|workspaceId|role|roles|isAdmin|SuperAdmin)\b/i,
|
|
3088
|
-
requireAll: [/\b(?:initializeApp|firebase|firestore|getFirestore
|
|
3142
|
+
requireAll: [/\b(?:initializeApp|firebase|firestore|getFirestore)\b[\s\S]{0,700}\b(?:apiKey|authDomain|projectId|databaseURL|storageBucket)\b|\b(?:apiKey|authDomain|projectId|databaseURL|storageBucket)\b[\s\S]{0,700}\b(?:firebase|firestore|getFirestore|initializeApp)\b|\bcreateClient\b[\s\S]{0,700}\b(?:supabase|SUPABASE_URL)\b|\b(?:supabase|SUPABASE_URL)\b[\s\S]{0,700}\bcreateClient\b/i],
|
|
3089
3143
|
message: "A browser artifact exposes Firebase/Supabase config together with sensitive collections or authorization fields."
|
|
3090
3144
|
})
|
|
3091
3145
|
});
|
|
@@ -3104,6 +3158,76 @@ const AUTH_FUNCTION_NAMES = new Set([
|
|
|
3104
3158
|
"getAuth",
|
|
3105
3159
|
"validateSession"
|
|
3106
3160
|
]);
|
|
3161
|
+
const AUTH_STRONG_TOKEN_PATTERN = /^auth(?:n|z|ed|enticate[ds]?|enticating|entication|orize[ds]?|orizing|orization|orizer)?$/;
|
|
3162
|
+
const AUTH_STANDALONE_NOUN_TOKENS = new Set([
|
|
3163
|
+
"signedin",
|
|
3164
|
+
"loggedin",
|
|
3165
|
+
"signin"
|
|
3166
|
+
]);
|
|
3167
|
+
const AUTH_ASSERTIVE_VERB_TOKENS = new Set([
|
|
3168
|
+
"require",
|
|
3169
|
+
"ensure",
|
|
3170
|
+
"assert",
|
|
3171
|
+
"verify",
|
|
3172
|
+
"validate",
|
|
3173
|
+
"check",
|
|
3174
|
+
"protect",
|
|
3175
|
+
"enforce",
|
|
3176
|
+
"guard",
|
|
3177
|
+
"gate",
|
|
3178
|
+
"restrict",
|
|
3179
|
+
"is",
|
|
3180
|
+
"has",
|
|
3181
|
+
"can",
|
|
3182
|
+
"must"
|
|
3183
|
+
]);
|
|
3184
|
+
const AUTH_GETTER_VERB_TOKENS = new Set([
|
|
3185
|
+
"get",
|
|
3186
|
+
"fetch",
|
|
3187
|
+
"load",
|
|
3188
|
+
"read",
|
|
3189
|
+
"resolve",
|
|
3190
|
+
"retrieve",
|
|
3191
|
+
"use"
|
|
3192
|
+
]);
|
|
3193
|
+
const AUTH_QUALIFIER_TOKENS = new Set([
|
|
3194
|
+
"current",
|
|
3195
|
+
"my",
|
|
3196
|
+
"own"
|
|
3197
|
+
]);
|
|
3198
|
+
const AUTH_STRONG_NOUN_TOKENS = new Set([
|
|
3199
|
+
"session",
|
|
3200
|
+
"sessions",
|
|
3201
|
+
"login",
|
|
3202
|
+
"admin",
|
|
3203
|
+
"admins",
|
|
3204
|
+
"superadmin",
|
|
3205
|
+
"superuser",
|
|
3206
|
+
"role",
|
|
3207
|
+
"roles",
|
|
3208
|
+
"permission",
|
|
3209
|
+
"permissions",
|
|
3210
|
+
"jwt",
|
|
3211
|
+
"identity",
|
|
3212
|
+
"principal",
|
|
3213
|
+
"credential",
|
|
3214
|
+
"credentials"
|
|
3215
|
+
]);
|
|
3216
|
+
const AUTH_WEAK_NOUN_TOKENS = new Set([
|
|
3217
|
+
"user",
|
|
3218
|
+
"users",
|
|
3219
|
+
"account",
|
|
3220
|
+
"accounts",
|
|
3221
|
+
"token",
|
|
3222
|
+
"tokens",
|
|
3223
|
+
"access",
|
|
3224
|
+
"me",
|
|
3225
|
+
"viewer",
|
|
3226
|
+
"caller",
|
|
3227
|
+
"subject",
|
|
3228
|
+
"scope",
|
|
3229
|
+
"scopes"
|
|
3230
|
+
]);
|
|
3107
3231
|
const GENERIC_AUTH_METHOD_NAMES = new Set(["getUser"]);
|
|
3108
3232
|
const AUTH_OBJECT_PATTERN = /(?:^|[._])(?:auth|authn|authz|clerk|session|jwt|firebase|supabase|nextauth|kinde|workos|stytch|descope|cognito|propelauth|lucia)/i;
|
|
3109
3233
|
const SECRET_PATTERNS = [
|
|
@@ -4202,7 +4326,7 @@ const asyncParallel = defineRule({
|
|
|
4202
4326
|
});
|
|
4203
4327
|
//#endregion
|
|
4204
4328
|
//#region src/plugin/rules/security/auth-token-in-web-storage.ts
|
|
4205
|
-
const MESSAGE$
|
|
4329
|
+
const MESSAGE$57 = "Storing an auth token in `localStorage`/`sessionStorage` exposes it to any XSS on the page: JavaScript can read web storage and exfiltrate the token. Keep tokens in an `HttpOnly`, `Secure`, `SameSite` cookie instead.";
|
|
4206
4330
|
const STORAGE_NAMES = new Set(["localStorage", "sessionStorage"]);
|
|
4207
4331
|
const STORAGE_GLOBALS = new Set([
|
|
4208
4332
|
"window",
|
|
@@ -4236,7 +4360,7 @@ const authTokenInWebStorage = defineRule({
|
|
|
4236
4360
|
if (!SENSITIVE_KEY_PATTERN.test(keyArgument.value)) return;
|
|
4237
4361
|
context.report({
|
|
4238
4362
|
node,
|
|
4239
|
-
message: MESSAGE$
|
|
4363
|
+
message: MESSAGE$57
|
|
4240
4364
|
});
|
|
4241
4365
|
},
|
|
4242
4366
|
AssignmentExpression(node) {
|
|
@@ -4247,7 +4371,7 @@ const authTokenInWebStorage = defineRule({
|
|
|
4247
4371
|
if (!propertyName || !SENSITIVE_KEY_PATTERN.test(propertyName)) return;
|
|
4248
4372
|
context.report({
|
|
4249
4373
|
node: target,
|
|
4250
|
-
message: MESSAGE$
|
|
4374
|
+
message: MESSAGE$57
|
|
4251
4375
|
});
|
|
4252
4376
|
}
|
|
4253
4377
|
})
|
|
@@ -4586,6 +4710,14 @@ const checkedRequiresOnchangeOrReadonly = defineRule({
|
|
|
4586
4710
|
}
|
|
4587
4711
|
});
|
|
4588
4712
|
//#endregion
|
|
4713
|
+
//#region src/plugin/utils/is-presentation-role.ts
|
|
4714
|
+
const isPresentationRole = (openingElement) => {
|
|
4715
|
+
const roleAttribute = hasJsxPropIgnoreCase(openingElement.attributes, "role");
|
|
4716
|
+
if (!roleAttribute) return false;
|
|
4717
|
+
const value = getJsxPropStringValue(roleAttribute);
|
|
4718
|
+
return value !== null && PRESENTATION_ROLES$1.has(value);
|
|
4719
|
+
};
|
|
4720
|
+
//#endregion
|
|
4589
4721
|
//#region src/plugin/utils/is-pure-event-blocker-handler.ts
|
|
4590
4722
|
const BLOCKER_METHOD_NAMES = new Set([
|
|
4591
4723
|
"stopPropagation",
|
|
@@ -4623,8 +4755,7 @@ const isPureEventBlockerHandler = (attribute) => {
|
|
|
4623
4755
|
};
|
|
4624
4756
|
//#endregion
|
|
4625
4757
|
//#region src/plugin/rules/a11y/click-events-have-key-events.ts
|
|
4626
|
-
const
|
|
4627
|
-
const MESSAGE$54 = "Keyboard users can't trigger this click handler because there's no keyboard one, so add `onKeyUp`, `onKeyDown`, or `onKeyPress`.";
|
|
4758
|
+
const MESSAGE$56 = "Keyboard users can't trigger this click handler because there's no keyboard one, so add `onKeyUp`, `onKeyDown`, or `onKeyPress`.";
|
|
4628
4759
|
const KEY_HANDLERS = [
|
|
4629
4760
|
"onKeyUp",
|
|
4630
4761
|
"onKeyDown",
|
|
@@ -4648,15 +4779,11 @@ const clickEventsHaveKeyEvents = defineRule({
|
|
|
4648
4779
|
if (!onClick) return;
|
|
4649
4780
|
if (isPureEventBlockerHandler(onClick)) return;
|
|
4650
4781
|
if (isHiddenFromScreenReader(node, context.settings)) return;
|
|
4651
|
-
|
|
4652
|
-
if (roleAttribute) {
|
|
4653
|
-
const roleValue = getJsxPropStringValue(roleAttribute);
|
|
4654
|
-
if (roleValue && PRESENTATION_ROLES$1.has(roleValue)) return;
|
|
4655
|
-
}
|
|
4782
|
+
if (isPresentationRole(node)) return;
|
|
4656
4783
|
if (KEY_HANDLERS.some((handler) => hasJsxPropIgnoreCase(node.attributes, handler))) return;
|
|
4657
4784
|
context.report({
|
|
4658
4785
|
node: node.name,
|
|
4659
|
-
message: MESSAGE$
|
|
4786
|
+
message: MESSAGE$56
|
|
4660
4787
|
});
|
|
4661
4788
|
} };
|
|
4662
4789
|
}
|
|
@@ -4771,7 +4898,7 @@ const isReactComponentName = (name) => {
|
|
|
4771
4898
|
};
|
|
4772
4899
|
//#endregion
|
|
4773
4900
|
//#region src/plugin/rules/a11y/control-has-associated-label.ts
|
|
4774
|
-
const MESSAGE$
|
|
4901
|
+
const MESSAGE$55 = "Blind users can't tell what this control does because screen readers find no label, so add visible text, `aria-label`, or `aria-labelledby`.";
|
|
4775
4902
|
const DEFAULT_IGNORE_ELEMENTS = ["link", "canvas"];
|
|
4776
4903
|
const DEFAULT_LABELLING_PROPS = [
|
|
4777
4904
|
"alt",
|
|
@@ -4932,7 +5059,7 @@ const controlHasAssociatedLabel = defineRule({
|
|
|
4932
5059
|
for (const child of node.children) if (checkChildForLabel(child, 1, checkContext)) return;
|
|
4933
5060
|
context.report({
|
|
4934
5061
|
node: opening,
|
|
4935
|
-
message: MESSAGE$
|
|
5062
|
+
message: MESSAGE$55
|
|
4936
5063
|
});
|
|
4937
5064
|
} };
|
|
4938
5065
|
}
|
|
@@ -5362,7 +5489,7 @@ const noVagueButtonLabel = defineRule({
|
|
|
5362
5489
|
const hasJsxSpreadAttribute$1 = (attributes) => attributes.some((attribute) => isNodeOfType(attribute, "JSXSpreadAttribute"));
|
|
5363
5490
|
//#endregion
|
|
5364
5491
|
//#region src/plugin/rules/a11y/dialog-has-accessible-name.ts
|
|
5365
|
-
const MESSAGE$
|
|
5492
|
+
const MESSAGE$54 = "This dialog has no accessible name, so screen readers announce it as just “dialog.” Add `aria-label` or point `aria-labelledby` at its heading.";
|
|
5366
5493
|
const DIALOG_ROLES = new Set(["dialog", "alertdialog"]);
|
|
5367
5494
|
const NAME_PROVIDING_ATTRIBUTES = [
|
|
5368
5495
|
"aria-label",
|
|
@@ -5385,7 +5512,7 @@ const dialogHasAccessibleName = defineRule({
|
|
|
5385
5512
|
if (NAME_PROVIDING_ATTRIBUTES.some((attribute) => hasJsxPropIgnoreCase(node.attributes, attribute))) return;
|
|
5386
5513
|
context.report({
|
|
5387
5514
|
node: node.name,
|
|
5388
|
-
message: MESSAGE$
|
|
5515
|
+
message: MESSAGE$54
|
|
5389
5516
|
});
|
|
5390
5517
|
} })
|
|
5391
5518
|
});
|
|
@@ -5424,7 +5551,7 @@ const isEs6Component = (node) => {
|
|
|
5424
5551
|
};
|
|
5425
5552
|
//#endregion
|
|
5426
5553
|
//#region src/plugin/rules/react-builtins/display-name.ts
|
|
5427
|
-
const MESSAGE$
|
|
5554
|
+
const MESSAGE$53 = "This component shows up as Anonymous in React DevTools because it has no `displayName`.";
|
|
5428
5555
|
const DEFAULT_ADDITIONAL_HOCS = [
|
|
5429
5556
|
"observer",
|
|
5430
5557
|
"lazy",
|
|
@@ -5623,11 +5750,11 @@ const displayName = defineRule({
|
|
|
5623
5750
|
category: "Architecture",
|
|
5624
5751
|
create: (context) => {
|
|
5625
5752
|
const settings = resolveSettings$44(context.settings);
|
|
5626
|
-
const ignoreNamed = settings.ignoreTranspilerName
|
|
5753
|
+
const ignoreNamed = !settings.ignoreTranspilerName;
|
|
5627
5754
|
const reportAt = (node) => {
|
|
5628
5755
|
context.report({
|
|
5629
5756
|
node,
|
|
5630
|
-
message: MESSAGE$
|
|
5757
|
+
message: MESSAGE$53
|
|
5631
5758
|
});
|
|
5632
5759
|
};
|
|
5633
5760
|
return {
|
|
@@ -7775,7 +7902,7 @@ const forbidElements = defineRule({
|
|
|
7775
7902
|
});
|
|
7776
7903
|
//#endregion
|
|
7777
7904
|
//#region src/plugin/rules/react-builtins/forward-ref-uses-ref.ts
|
|
7778
|
-
const MESSAGE$
|
|
7905
|
+
const MESSAGE$52 = "The parent can't reach this component's node because the `forwardRef` wrapper ignores `ref`.";
|
|
7779
7906
|
const forwardRefUsesRef = defineRule({
|
|
7780
7907
|
id: "forward-ref-uses-ref",
|
|
7781
7908
|
title: "forwardRef without ref parameter",
|
|
@@ -7795,7 +7922,7 @@ const forwardRefUsesRef = defineRule({
|
|
|
7795
7922
|
if (isNodeOfType(onlyParam, "RestElement")) return;
|
|
7796
7923
|
context.report({
|
|
7797
7924
|
node: inner,
|
|
7798
|
-
message: MESSAGE$
|
|
7925
|
+
message: MESSAGE$52
|
|
7799
7926
|
});
|
|
7800
7927
|
} })
|
|
7801
7928
|
});
|
|
@@ -7832,7 +7959,7 @@ const gitProviderUrlInjectionRisk = defineRule({
|
|
|
7832
7959
|
});
|
|
7833
7960
|
//#endregion
|
|
7834
7961
|
//#region src/plugin/rules/a11y/heading-has-content.ts
|
|
7835
|
-
const MESSAGE$
|
|
7962
|
+
const MESSAGE$51 = "Blind users can't use this heading to navigate because screen readers skip it empty, so add text, `aria-label`, or `aria-labelledby`.";
|
|
7836
7963
|
const DEFAULT_HEADING_TAGS = [
|
|
7837
7964
|
"h1",
|
|
7838
7965
|
"h2",
|
|
@@ -7865,7 +7992,7 @@ const headingHasContent = defineRule({
|
|
|
7865
7992
|
if (isHiddenFromScreenReader(node, context.settings)) return;
|
|
7866
7993
|
context.report({
|
|
7867
7994
|
node,
|
|
7868
|
-
message: MESSAGE$
|
|
7995
|
+
message: MESSAGE$51
|
|
7869
7996
|
});
|
|
7870
7997
|
} };
|
|
7871
7998
|
}
|
|
@@ -8003,7 +8130,7 @@ const hooksNoNanInDeps = defineRule({
|
|
|
8003
8130
|
});
|
|
8004
8131
|
//#endregion
|
|
8005
8132
|
//#region src/plugin/rules/a11y/html-has-lang.ts
|
|
8006
|
-
const MESSAGE$
|
|
8133
|
+
const MESSAGE$50 = "Screen readers may mispronounce this page because it doesn't declare a language, so add a `lang` attribute like `en`.";
|
|
8007
8134
|
const resolveSettings$38 = (settings) => {
|
|
8008
8135
|
const reactDoctor = settings?.["react-doctor"];
|
|
8009
8136
|
return { htmlTags: (typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.htmlHasLang ?? {} : {}).htmlTags ?? ["html"] };
|
|
@@ -8046,26 +8173,17 @@ const htmlHasLang = defineRule({
|
|
|
8046
8173
|
return { JSXOpeningElement(node) {
|
|
8047
8174
|
const tag = getElementType(node, context.settings);
|
|
8048
8175
|
if (!tagSet.has(tag)) return;
|
|
8049
|
-
const hasSpread = node.attributes.some((attribute) => isNodeOfType(attribute, "JSXSpreadAttribute"));
|
|
8050
8176
|
const lang = hasJsxPropIgnoreCase(node.attributes, "lang");
|
|
8051
8177
|
if (!lang) {
|
|
8052
8178
|
context.report({
|
|
8053
8179
|
node: node.name,
|
|
8054
|
-
message: MESSAGE$
|
|
8180
|
+
message: MESSAGE$50
|
|
8055
8181
|
});
|
|
8056
8182
|
return;
|
|
8057
8183
|
}
|
|
8058
|
-
|
|
8059
|
-
|
|
8060
|
-
|
|
8061
|
-
node: lang,
|
|
8062
|
-
message: MESSAGE$48
|
|
8063
|
-
});
|
|
8064
|
-
return;
|
|
8065
|
-
}
|
|
8066
|
-
if (hasSpread && !lang) context.report({
|
|
8067
|
-
node: node.name,
|
|
8068
|
-
message: MESSAGE$48
|
|
8184
|
+
if (evaluateLang(lang.value) === "empty") context.report({
|
|
8185
|
+
node: lang,
|
|
8186
|
+
message: MESSAGE$50
|
|
8069
8187
|
});
|
|
8070
8188
|
} };
|
|
8071
8189
|
}
|
|
@@ -8279,7 +8397,7 @@ const htmlNoNestedInteractive = defineRule({
|
|
|
8279
8397
|
});
|
|
8280
8398
|
//#endregion
|
|
8281
8399
|
//#region src/plugin/rules/a11y/iframe-has-title.ts
|
|
8282
|
-
const MESSAGE$
|
|
8400
|
+
const MESSAGE$49 = "Screen reader users cannot identify this `<iframe>` because it has no title. Add a `title` that describes its content.";
|
|
8283
8401
|
const evaluateTitleValue = (value) => {
|
|
8284
8402
|
if (!value) return "missing";
|
|
8285
8403
|
if (isNodeOfType(value, "Literal")) {
|
|
@@ -8319,14 +8437,14 @@ const iframeHasTitle = defineRule({
|
|
|
8319
8437
|
if (!titleAttr) {
|
|
8320
8438
|
if (hasSpread || tag === "iframe") context.report({
|
|
8321
8439
|
node: node.name,
|
|
8322
|
-
message: MESSAGE$
|
|
8440
|
+
message: MESSAGE$49
|
|
8323
8441
|
});
|
|
8324
8442
|
return;
|
|
8325
8443
|
}
|
|
8326
8444
|
const verdict = evaluateTitleValue(titleAttr.value);
|
|
8327
8445
|
if (verdict === "missing" || verdict === "empty") context.report({
|
|
8328
8446
|
node: titleAttr,
|
|
8329
|
-
message: MESSAGE$
|
|
8447
|
+
message: MESSAGE$49
|
|
8330
8448
|
});
|
|
8331
8449
|
} })
|
|
8332
8450
|
});
|
|
@@ -8430,7 +8548,7 @@ const iframeMissingSandbox = defineRule({
|
|
|
8430
8548
|
});
|
|
8431
8549
|
//#endregion
|
|
8432
8550
|
//#region src/plugin/rules/a11y/img-redundant-alt.ts
|
|
8433
|
-
const MESSAGE$
|
|
8551
|
+
const MESSAGE$48 = "Screen reader users hear \"image\" or \"photo\" twice because they already announce it, so describe what the image shows instead.";
|
|
8434
8552
|
const DEFAULT_COMPONENTS = ["img"];
|
|
8435
8553
|
const DEFAULT_REDUNDANT_WORDS = [
|
|
8436
8554
|
"image",
|
|
@@ -8495,7 +8613,7 @@ const imgRedundantAlt = defineRule({
|
|
|
8495
8613
|
if (!altAttribute) return;
|
|
8496
8614
|
if (altValueRedundant(altAttribute, settings.words)) context.report({
|
|
8497
8615
|
node: altAttribute,
|
|
8498
|
-
message: MESSAGE$
|
|
8616
|
+
message: MESSAGE$48
|
|
8499
8617
|
});
|
|
8500
8618
|
} };
|
|
8501
8619
|
}
|
|
@@ -8778,14 +8896,6 @@ const isNonInteractiveElement = (elementType, openingElement) => {
|
|
|
8778
8896
|
//#region src/plugin/utils/is-non-interactive-role.ts
|
|
8779
8897
|
const isNonInteractiveRole = (role) => NON_INTERACTIVE_ROLES.has(role);
|
|
8780
8898
|
//#endregion
|
|
8781
|
-
//#region src/plugin/utils/is-presentation-role.ts
|
|
8782
|
-
const isPresentationRole = (openingElement) => {
|
|
8783
|
-
const roleAttribute = hasJsxPropIgnoreCase(openingElement.attributes, "role");
|
|
8784
|
-
if (!roleAttribute) return false;
|
|
8785
|
-
const value = getJsxPropStringValue(roleAttribute);
|
|
8786
|
-
return value !== null && PRESENTATION_ROLES$2.has(value);
|
|
8787
|
-
};
|
|
8788
|
-
//#endregion
|
|
8789
8899
|
//#region src/plugin/rules/a11y/interactive-supports-focus.ts
|
|
8790
8900
|
const buildTabbableMessage = (role) => `Keyboard users can't tab to this '${role}' because it isn't focusable, so add \`tabIndex={0}\`.`;
|
|
8791
8901
|
const buildFocusableMessage = (role) => `Keyboard users can't focus this '${role}' because it can't receive focus, so add \`tabIndex={0}\` or \`tabIndex={-1}\`.`;
|
|
@@ -10560,6 +10670,24 @@ const hasJsxKeyAttribute = (openingElement) => {
|
|
|
10560
10670
|
return false;
|
|
10561
10671
|
};
|
|
10562
10672
|
//#endregion
|
|
10673
|
+
//#region src/plugin/utils/is-non-children-jsx-attribute-value.ts
|
|
10674
|
+
const ascendThroughJsxValueWrappers = (node) => {
|
|
10675
|
+
let current = node;
|
|
10676
|
+
while (current.parent) {
|
|
10677
|
+
const parent = current.parent;
|
|
10678
|
+
if (!(isNodeOfType(parent, "ChainExpression") || isNodeOfType(parent, "TSAsExpression") || isNodeOfType(parent, "TSSatisfiesExpression") || isNodeOfType(parent, "TSNonNullExpression") || isNodeOfType(parent, "LogicalExpression") || isNodeOfType(parent, "ConditionalExpression") && parent.test !== current)) break;
|
|
10679
|
+
current = parent;
|
|
10680
|
+
}
|
|
10681
|
+
return current;
|
|
10682
|
+
};
|
|
10683
|
+
const isNonChildrenJsxAttributeValue = (node) => {
|
|
10684
|
+
const container = ascendThroughJsxValueWrappers(node).parent;
|
|
10685
|
+
if (!container || !isNodeOfType(container, "JSXExpressionContainer")) return false;
|
|
10686
|
+
const attribute = container.parent;
|
|
10687
|
+
if (!attribute || !isNodeOfType(attribute, "JSXAttribute")) return false;
|
|
10688
|
+
return getJsxAttributeName(attribute.name) !== "children";
|
|
10689
|
+
};
|
|
10690
|
+
//#endregion
|
|
10563
10691
|
//#region src/plugin/rules/react-builtins/jsx-key.ts
|
|
10564
10692
|
const ITERATOR_METHOD_NAMES = new Set([
|
|
10565
10693
|
"map",
|
|
@@ -10598,6 +10726,7 @@ const findEnclosingIteratorContext = (jsxNode) => {
|
|
|
10598
10726
|
const arrayParent = parent.parent;
|
|
10599
10727
|
if (arrayParent && isNodeOfType(arrayParent, "Property")) return null;
|
|
10600
10728
|
if (arrayParent && isNodeOfType(arrayParent, "ArrayExpression")) return null;
|
|
10729
|
+
if (isNonChildrenJsxAttributeValue(parent)) return null;
|
|
10601
10730
|
return { kind: "array" };
|
|
10602
10731
|
} else if (isNodeOfType(parent, "CallExpression")) {
|
|
10603
10732
|
const callee = parent.callee;
|
|
@@ -10610,10 +10739,13 @@ const findEnclosingIteratorContext = (jsxNode) => {
|
|
|
10610
10739
|
if (!targetArg) return null;
|
|
10611
10740
|
let walker = current;
|
|
10612
10741
|
while (walker && walker !== parent) {
|
|
10613
|
-
if (walker === targetArg)
|
|
10614
|
-
|
|
10615
|
-
|
|
10616
|
-
|
|
10742
|
+
if (walker === targetArg) {
|
|
10743
|
+
if (isNonChildrenJsxAttributeValue(parent)) return null;
|
|
10744
|
+
return {
|
|
10745
|
+
kind: "iterator",
|
|
10746
|
+
callExpression: parent
|
|
10747
|
+
};
|
|
10748
|
+
}
|
|
10617
10749
|
walker = walker.parent ?? null;
|
|
10618
10750
|
}
|
|
10619
10751
|
return null;
|
|
@@ -10852,7 +10984,7 @@ const jsxMaxDepth = defineRule({
|
|
|
10852
10984
|
});
|
|
10853
10985
|
//#endregion
|
|
10854
10986
|
//#region src/plugin/rules/react-builtins/jsx-no-comment-textnodes.ts
|
|
10855
|
-
const MESSAGE$
|
|
10987
|
+
const MESSAGE$47 = "Your users see this comment as text on the page because `//` & `/*` aren't hidden in JSX.";
|
|
10856
10988
|
const LITERAL_TEXT_TAGS = new Set([
|
|
10857
10989
|
"code",
|
|
10858
10990
|
"pre",
|
|
@@ -10888,7 +11020,7 @@ const jsxNoCommentTextnodes = defineRule({
|
|
|
10888
11020
|
if (isInsideLiteralTextTag(node)) return;
|
|
10889
11021
|
context.report({
|
|
10890
11022
|
node,
|
|
10891
|
-
message: MESSAGE$
|
|
11023
|
+
message: MESSAGE$47
|
|
10892
11024
|
});
|
|
10893
11025
|
} })
|
|
10894
11026
|
});
|
|
@@ -10919,7 +11051,7 @@ const isInsideFunctionScope = (node) => {
|
|
|
10919
11051
|
};
|
|
10920
11052
|
//#endregion
|
|
10921
11053
|
//#region src/plugin/rules/react-builtins/jsx-no-constructed-context-values.ts
|
|
10922
|
-
const MESSAGE$
|
|
11054
|
+
const MESSAGE$46 = "Every reader of this context redraws on each render because you build its `value` inline.";
|
|
10923
11055
|
const CONTEXT_MODULES$1 = [
|
|
10924
11056
|
"react",
|
|
10925
11057
|
"use-context-selector",
|
|
@@ -11017,7 +11149,7 @@ const jsxNoConstructedContextValues = defineRule({
|
|
|
11017
11149
|
if (!isConstructedValue(innerExpression)) continue;
|
|
11018
11150
|
context.report({
|
|
11019
11151
|
node: attribute,
|
|
11020
|
-
message: MESSAGE$
|
|
11152
|
+
message: MESSAGE$46
|
|
11021
11153
|
});
|
|
11022
11154
|
}
|
|
11023
11155
|
}
|
|
@@ -11103,7 +11235,7 @@ const isJsxAttributeOnIntrinsicHtmlElement = (attribute) => {
|
|
|
11103
11235
|
};
|
|
11104
11236
|
//#endregion
|
|
11105
11237
|
//#region src/plugin/rules/react-builtins/jsx-no-jsx-as-prop.ts
|
|
11106
|
-
const MESSAGE$
|
|
11238
|
+
const MESSAGE$45 = "This child redraws every render because the prop gets brand new JSX each time.";
|
|
11107
11239
|
const KNOWN_SLOT_PROP_NAMES = new Set([
|
|
11108
11240
|
"icon",
|
|
11109
11241
|
"Icon",
|
|
@@ -11372,7 +11504,7 @@ const jsxNoJsxAsProp = defineRule({
|
|
|
11372
11504
|
if (!isJsxProducingExpression(expressionNode) && !followsRenderLocalJsxBinding(expressionNode, node)) return;
|
|
11373
11505
|
context.report({
|
|
11374
11506
|
node,
|
|
11375
|
-
message: MESSAGE$
|
|
11507
|
+
message: MESSAGE$45
|
|
11376
11508
|
});
|
|
11377
11509
|
}
|
|
11378
11510
|
};
|
|
@@ -11660,7 +11792,7 @@ const DATA_ARRAY_PROP_SUFFIXES = [
|
|
|
11660
11792
|
];
|
|
11661
11793
|
//#endregion
|
|
11662
11794
|
//#region src/plugin/rules/react-builtins/jsx-no-new-array-as-prop.ts
|
|
11663
|
-
const MESSAGE$
|
|
11795
|
+
const MESSAGE$44 = "This child redraws every render because the prop gets a brand new array each time.";
|
|
11664
11796
|
const isDataArrayPropName = (propName) => {
|
|
11665
11797
|
if (DATA_ARRAY_PROP_NAMES.has(propName)) return true;
|
|
11666
11798
|
for (const suffix of DATA_ARRAY_PROP_SUFFIXES) if (propName.length > suffix.length && propName.endsWith(suffix)) return true;
|
|
@@ -11744,7 +11876,7 @@ const jsxNoNewArrayAsProp = defineRule({
|
|
|
11744
11876
|
if (!isArrayProducingExpression(expressionNode) && !followsRenderLocalArrayBinding(expressionNode, node)) return;
|
|
11745
11877
|
context.report({
|
|
11746
11878
|
node,
|
|
11747
|
-
message: MESSAGE$
|
|
11879
|
+
message: MESSAGE$44
|
|
11748
11880
|
});
|
|
11749
11881
|
}
|
|
11750
11882
|
};
|
|
@@ -12002,7 +12134,7 @@ const SAFE_RECEIVER_NAMES = new Set([
|
|
|
12002
12134
|
]);
|
|
12003
12135
|
//#endregion
|
|
12004
12136
|
//#region src/plugin/rules/react-builtins/jsx-no-new-function-as-prop.ts
|
|
12005
|
-
const MESSAGE$
|
|
12137
|
+
const MESSAGE$43 = "This child redraws every render because the prop gets a brand new function each time.";
|
|
12006
12138
|
const isAccessorPredicateName = (propName) => {
|
|
12007
12139
|
for (const prefix of ACCESSOR_PREDICATE_PREFIXES) {
|
|
12008
12140
|
if (propName.length <= prefix.length) continue;
|
|
@@ -12208,7 +12340,7 @@ const jsxNoNewFunctionAsProp = defineRule({
|
|
|
12208
12340
|
if (!isFunctionProducingExpression(expressionNode) && !followsRenderLocalFunctionBinding(expressionNode, node)) return;
|
|
12209
12341
|
context.report({
|
|
12210
12342
|
node,
|
|
12211
|
-
message: MESSAGE$
|
|
12343
|
+
message: MESSAGE$43
|
|
12212
12344
|
});
|
|
12213
12345
|
}
|
|
12214
12346
|
};
|
|
@@ -12428,7 +12560,7 @@ const CONFIG_OBJECT_PROP_SUFFIXES = [
|
|
|
12428
12560
|
];
|
|
12429
12561
|
//#endregion
|
|
12430
12562
|
//#region src/plugin/rules/react-builtins/jsx-no-new-object-as-prop.ts
|
|
12431
|
-
const MESSAGE$
|
|
12563
|
+
const MESSAGE$42 = "This child redraws every render because the prop gets a brand new object each time.";
|
|
12432
12564
|
const isConfigObjectPropName = (propName) => {
|
|
12433
12565
|
if (CONFIG_OBJECT_PROP_NAMES.has(propName)) return true;
|
|
12434
12566
|
for (const suffix of CONFIG_OBJECT_PROP_SUFFIXES) if (propName.length > suffix.length && propName.endsWith(suffix)) return true;
|
|
@@ -12516,7 +12648,7 @@ const jsxNoNewObjectAsProp = defineRule({
|
|
|
12516
12648
|
if (!isObjectProducingExpression(expressionNode) && !followsRenderLocalObjectBinding(expressionNode, node)) return;
|
|
12517
12649
|
context.report({
|
|
12518
12650
|
node,
|
|
12519
|
-
message: MESSAGE$
|
|
12651
|
+
message: MESSAGE$42
|
|
12520
12652
|
});
|
|
12521
12653
|
}
|
|
12522
12654
|
};
|
|
@@ -12524,7 +12656,7 @@ const jsxNoNewObjectAsProp = defineRule({
|
|
|
12524
12656
|
});
|
|
12525
12657
|
//#endregion
|
|
12526
12658
|
//#region src/plugin/rules/react-builtins/jsx-no-script-url.ts
|
|
12527
|
-
const MESSAGE$
|
|
12659
|
+
const MESSAGE$41 = "A `javascript:` URL is an XSS hole that runs injected input as code.";
|
|
12528
12660
|
const JAVASCRIPT_URL_PATTERN = /j[\r\n\t]*a[\r\n\t]*v[\r\n\t]*a[\r\n\t]*s[\r\n\t]*c[\r\n\t]*r[\r\n\t]*i[\r\n\t]*p[\r\n\t]*t[\r\n\t]*:/i;
|
|
12529
12661
|
const resolveSettings$28 = (settings) => {
|
|
12530
12662
|
const reactDoctor = settings?.["react-doctor"];
|
|
@@ -12565,7 +12697,7 @@ const jsxNoScriptUrl = defineRule({
|
|
|
12565
12697
|
if (!value || !isNodeOfType(value, "Literal") || typeof value.value !== "string") continue;
|
|
12566
12698
|
if (JAVASCRIPT_URL_PATTERN.test(value.value)) context.report({
|
|
12567
12699
|
node: attribute,
|
|
12568
|
-
message: MESSAGE$
|
|
12700
|
+
message: MESSAGE$41
|
|
12569
12701
|
});
|
|
12570
12702
|
}
|
|
12571
12703
|
} };
|
|
@@ -12880,7 +13012,7 @@ const jsxPropsNoSpreadMulti = defineRule({
|
|
|
12880
13012
|
});
|
|
12881
13013
|
//#endregion
|
|
12882
13014
|
//#region src/plugin/rules/react-builtins/jsx-props-no-spreading.ts
|
|
12883
|
-
const MESSAGE$
|
|
13015
|
+
const MESSAGE$40 = "You can't tell what props reach this element when you spread them.";
|
|
12884
13016
|
const resolveSettings$25 = (settings) => {
|
|
12885
13017
|
const reactDoctor = settings?.["react-doctor"];
|
|
12886
13018
|
const ruleSettings = typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.jsxPropsNoSpreading ?? {} : {};
|
|
@@ -12921,7 +13053,7 @@ const jsxPropsNoSpreading = defineRule({
|
|
|
12921
13053
|
}
|
|
12922
13054
|
context.report({
|
|
12923
13055
|
node: attribute,
|
|
12924
|
-
message: MESSAGE$
|
|
13056
|
+
message: MESSAGE$40
|
|
12925
13057
|
});
|
|
12926
13058
|
}
|
|
12927
13059
|
} };
|
|
@@ -13149,7 +13281,7 @@ const labelHasAssociatedControl = defineRule({
|
|
|
13149
13281
|
});
|
|
13150
13282
|
//#endregion
|
|
13151
13283
|
//#region src/plugin/rules/a11y/lang.ts
|
|
13152
|
-
const MESSAGE$
|
|
13284
|
+
const MESSAGE$39 = "Screen readers can't pick the right voice because this `lang` isn't a real language code, so use a valid one like `en` or `en-US`.";
|
|
13153
13285
|
const COMMON_LANGUAGE_PRIMARY_TAGS = new Set([
|
|
13154
13286
|
"aa",
|
|
13155
13287
|
"ab",
|
|
@@ -13361,7 +13493,7 @@ const lang = defineRule({
|
|
|
13361
13493
|
if (expression.type === "Identifier" && expression.name === "undefined" || expression.type === "Literal" && expression.value === null) {
|
|
13362
13494
|
context.report({
|
|
13363
13495
|
node: langAttr,
|
|
13364
|
-
message: MESSAGE$
|
|
13496
|
+
message: MESSAGE$39
|
|
13365
13497
|
});
|
|
13366
13498
|
return;
|
|
13367
13499
|
}
|
|
@@ -13370,7 +13502,7 @@ const lang = defineRule({
|
|
|
13370
13502
|
if (value === null) return;
|
|
13371
13503
|
if (!isValidLangTag(value)) context.report({
|
|
13372
13504
|
node: langAttr,
|
|
13373
|
-
message: MESSAGE$
|
|
13505
|
+
message: MESSAGE$39
|
|
13374
13506
|
});
|
|
13375
13507
|
} })
|
|
13376
13508
|
});
|
|
@@ -13396,6 +13528,7 @@ const mcpToolCapabilityRisk = defineRule({
|
|
|
13396
13528
|
shouldScan: (file) => isProductionSourcePath(file.relativePath),
|
|
13397
13529
|
pattern: /\bserver\.\s*tool\s*\(|\bregisterTool\s*\(|\bsetRequestHandler\s*\(\s*CallToolRequestSchema/,
|
|
13398
13530
|
requireAll: [/\bfrom\s+["']@modelcontextprotocol\/sdk[^"']*["']|\bMcpServer\b|\bMcpAgent\b/, AGENT_TOOL_DANGEROUS_CAPABILITY_PATTERN],
|
|
13531
|
+
ignoreStringLiterals: true,
|
|
13399
13532
|
message: "An MCP tool/resource/prompt handler appears to expose file, shell, network, or code-execution capability."
|
|
13400
13533
|
})
|
|
13401
13534
|
});
|
|
@@ -13414,7 +13547,7 @@ const mdxSsrExecutionRisk = defineRule({
|
|
|
13414
13547
|
});
|
|
13415
13548
|
//#endregion
|
|
13416
13549
|
//#region src/plugin/rules/a11y/media-has-caption.ts
|
|
13417
|
-
const MESSAGE$
|
|
13550
|
+
const MESSAGE$38 = "Deaf and hard-of-hearing users need captions for this media. Add a `<track kind=\"captions\">` inside the `<audio>` or `<video>`.";
|
|
13418
13551
|
const DEFAULT_AUDIO = ["audio"];
|
|
13419
13552
|
const DEFAULT_VIDEO = ["video"];
|
|
13420
13553
|
const DEFAULT_TRACK = ["track"];
|
|
@@ -13455,7 +13588,7 @@ const mediaHasCaption = defineRule({
|
|
|
13455
13588
|
if (!parent || !isNodeOfType(parent, "JSXElement")) {
|
|
13456
13589
|
context.report({
|
|
13457
13590
|
node: node.name,
|
|
13458
|
-
message: MESSAGE$
|
|
13591
|
+
message: MESSAGE$38
|
|
13459
13592
|
});
|
|
13460
13593
|
return;
|
|
13461
13594
|
}
|
|
@@ -13472,7 +13605,7 @@ const mediaHasCaption = defineRule({
|
|
|
13472
13605
|
return kindValue.value.toLowerCase() === "captions";
|
|
13473
13606
|
})) context.report({
|
|
13474
13607
|
node: node.name,
|
|
13475
|
-
message: MESSAGE$
|
|
13608
|
+
message: MESSAGE$38
|
|
13476
13609
|
});
|
|
13477
13610
|
} };
|
|
13478
13611
|
}
|
|
@@ -15273,7 +15406,7 @@ const nextjsNoVercelOgImport = defineRule({
|
|
|
15273
15406
|
});
|
|
15274
15407
|
//#endregion
|
|
15275
15408
|
//#region src/plugin/rules/a11y/no-access-key.ts
|
|
15276
|
-
const MESSAGE$
|
|
15409
|
+
const MESSAGE$37 = "Screen reader users can lose their shortcuts because `accessKey` clashes with them, so remove it.";
|
|
15277
15410
|
const isUndefinedIdentifier = (expression) => isNodeOfType(expression, "Identifier") && expression.name === "undefined";
|
|
15278
15411
|
const noAccessKey = defineRule({
|
|
15279
15412
|
id: "no-access-key",
|
|
@@ -15290,7 +15423,7 @@ const noAccessKey = defineRule({
|
|
|
15290
15423
|
if (isNodeOfType(attributeValue, "Literal") && typeof attributeValue.value === "string") {
|
|
15291
15424
|
context.report({
|
|
15292
15425
|
node: accessKey,
|
|
15293
|
-
message: MESSAGE$
|
|
15426
|
+
message: MESSAGE$37
|
|
15294
15427
|
});
|
|
15295
15428
|
return;
|
|
15296
15429
|
}
|
|
@@ -15300,7 +15433,7 @@ const noAccessKey = defineRule({
|
|
|
15300
15433
|
if (isUndefinedIdentifier(expression)) return;
|
|
15301
15434
|
context.report({
|
|
15302
15435
|
node: accessKey,
|
|
15303
|
-
message: MESSAGE$
|
|
15436
|
+
message: MESSAGE$37
|
|
15304
15437
|
});
|
|
15305
15438
|
}
|
|
15306
15439
|
} })
|
|
@@ -15783,7 +15916,7 @@ const noAdjustStateOnPropChange = defineRule({
|
|
|
15783
15916
|
});
|
|
15784
15917
|
//#endregion
|
|
15785
15918
|
//#region src/plugin/rules/a11y/no-aria-hidden-on-focusable.ts
|
|
15786
|
-
const MESSAGE$
|
|
15919
|
+
const MESSAGE$36 = "Screen reader users tab to this focusable element but hear nothing because `aria-hidden` skips it, so remove `aria-hidden` or stop it being focusable.";
|
|
15787
15920
|
const noAriaHiddenOnFocusable = defineRule({
|
|
15788
15921
|
id: "no-aria-hidden-on-focusable",
|
|
15789
15922
|
title: "aria-hidden on focusable element",
|
|
@@ -15810,7 +15943,7 @@ const noAriaHiddenOnFocusable = defineRule({
|
|
|
15810
15943
|
const isImplicitlyFocusable = isInteractiveElement(tag, node);
|
|
15811
15944
|
if (isExplicitlyFocusable || isImplicitlyFocusable) context.report({
|
|
15812
15945
|
node: ariaHidden,
|
|
15813
|
-
message: MESSAGE$
|
|
15946
|
+
message: MESSAGE$36
|
|
15814
15947
|
});
|
|
15815
15948
|
} })
|
|
15816
15949
|
});
|
|
@@ -16178,7 +16311,7 @@ const noArrayIndexAsKey = defineRule({
|
|
|
16178
16311
|
});
|
|
16179
16312
|
//#endregion
|
|
16180
16313
|
//#region src/plugin/rules/react-builtins/no-array-index-key.ts
|
|
16181
|
-
const MESSAGE$
|
|
16314
|
+
const MESSAGE$35 = "Your users can see & submit the wrong data when this list reorders.";
|
|
16182
16315
|
const SECOND_INDEX_METHODS = new Set([
|
|
16183
16316
|
"every",
|
|
16184
16317
|
"filter",
|
|
@@ -16382,7 +16515,7 @@ const noArrayIndexKey = defineRule({
|
|
|
16382
16515
|
}
|
|
16383
16516
|
context.report({
|
|
16384
16517
|
node: keyAttribute,
|
|
16385
|
-
message: MESSAGE$
|
|
16518
|
+
message: MESSAGE$35
|
|
16386
16519
|
});
|
|
16387
16520
|
},
|
|
16388
16521
|
CallExpression(node) {
|
|
@@ -16402,7 +16535,7 @@ const noArrayIndexKey = defineRule({
|
|
|
16402
16535
|
if (propName !== "key") continue;
|
|
16403
16536
|
if (expressionUsesIndex(property.value, indexBinding.name)) context.report({
|
|
16404
16537
|
node: property,
|
|
16405
|
-
message: MESSAGE$
|
|
16538
|
+
message: MESSAGE$35
|
|
16406
16539
|
});
|
|
16407
16540
|
}
|
|
16408
16541
|
}
|
|
@@ -16410,7 +16543,7 @@ const noArrayIndexKey = defineRule({
|
|
|
16410
16543
|
});
|
|
16411
16544
|
//#endregion
|
|
16412
16545
|
//#region src/plugin/rules/state-and-effects/no-async-effect-callback.ts
|
|
16413
|
-
const MESSAGE$
|
|
16546
|
+
const MESSAGE$34 = "The `useEffect` callback is `async`, so it returns a Promise instead of a cleanup function. React calls that Promise as cleanup (a no-op) and the effect can race on unmount. Put the async work in an inner function and call it.";
|
|
16414
16547
|
const noAsyncEffectCallback = defineRule({
|
|
16415
16548
|
id: "no-async-effect-callback",
|
|
16416
16549
|
title: "Async effect callback",
|
|
@@ -16424,13 +16557,13 @@ const noAsyncEffectCallback = defineRule({
|
|
|
16424
16557
|
if (!callback.async) return;
|
|
16425
16558
|
context.report({
|
|
16426
16559
|
node: callback,
|
|
16427
|
-
message: MESSAGE$
|
|
16560
|
+
message: MESSAGE$34
|
|
16428
16561
|
});
|
|
16429
16562
|
} })
|
|
16430
16563
|
});
|
|
16431
16564
|
//#endregion
|
|
16432
16565
|
//#region src/plugin/rules/a11y/no-autofocus.ts
|
|
16433
|
-
const MESSAGE$
|
|
16566
|
+
const MESSAGE$33 = "`autoFocus` moves focus on load, which can disrupt screen reader and keyboard users. Remove it and let users choose where to focus.";
|
|
16434
16567
|
const resolveSettings$21 = (settings) => {
|
|
16435
16568
|
const reactDoctor = settings?.["react-doctor"];
|
|
16436
16569
|
return { ignoreNonDOM: (typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.noAutofocus ?? {} : {}).ignoreNonDOM ?? true };
|
|
@@ -16486,7 +16619,7 @@ const noAutofocus = defineRule({
|
|
|
16486
16619
|
}
|
|
16487
16620
|
context.report({
|
|
16488
16621
|
node: autoFocusAttribute,
|
|
16489
|
-
message: MESSAGE$
|
|
16622
|
+
message: MESSAGE$33
|
|
16490
16623
|
});
|
|
16491
16624
|
} };
|
|
16492
16625
|
}
|
|
@@ -16990,7 +17123,7 @@ const noChainStateUpdates = defineRule({
|
|
|
16990
17123
|
});
|
|
16991
17124
|
//#endregion
|
|
16992
17125
|
//#region src/plugin/rules/react-builtins/no-children-prop.ts
|
|
16993
|
-
const MESSAGE$
|
|
17126
|
+
const MESSAGE$32 = "A `children` prop can override or hide nested children, so the component may render different content than the JSX shows.";
|
|
16994
17127
|
const noChildrenProp = defineRule({
|
|
16995
17128
|
id: "no-children-prop",
|
|
16996
17129
|
title: "Children passed as a prop",
|
|
@@ -17002,7 +17135,7 @@ const noChildrenProp = defineRule({
|
|
|
17002
17135
|
if (node.name.name !== "children") return;
|
|
17003
17136
|
context.report({
|
|
17004
17137
|
node: node.name,
|
|
17005
|
-
message: MESSAGE$
|
|
17138
|
+
message: MESSAGE$32
|
|
17006
17139
|
});
|
|
17007
17140
|
},
|
|
17008
17141
|
CallExpression(node) {
|
|
@@ -17015,7 +17148,7 @@ const noChildrenProp = defineRule({
|
|
|
17015
17148
|
const propertyKey = property.key;
|
|
17016
17149
|
if (isNodeOfType(propertyKey, "Identifier") && propertyKey.name === "children" || isNodeOfType(propertyKey, "Literal") && propertyKey.value === "children") context.report({
|
|
17017
17150
|
node: propertyKey,
|
|
17018
|
-
message: MESSAGE$
|
|
17151
|
+
message: MESSAGE$32
|
|
17019
17152
|
});
|
|
17020
17153
|
}
|
|
17021
17154
|
}
|
|
@@ -17023,7 +17156,7 @@ const noChildrenProp = defineRule({
|
|
|
17023
17156
|
});
|
|
17024
17157
|
//#endregion
|
|
17025
17158
|
//#region src/plugin/rules/react-builtins/no-clone-element.ts
|
|
17026
|
-
const MESSAGE$
|
|
17159
|
+
const MESSAGE$31 = "`React.cloneElement` couples the parent to the child's prop shape, so child prop changes can silently break injected behavior.";
|
|
17027
17160
|
const noCloneElement = defineRule({
|
|
17028
17161
|
id: "no-clone-element",
|
|
17029
17162
|
title: "cloneElement makes child props fragile",
|
|
@@ -17036,7 +17169,7 @@ const noCloneElement = defineRule({
|
|
|
17036
17169
|
if (isNodeOfType(callee, "Identifier") && callee.name === "cloneElement") {
|
|
17037
17170
|
if (isImportedFromModule(node, "cloneElement", "react")) context.report({
|
|
17038
17171
|
node: callee,
|
|
17039
|
-
message: MESSAGE$
|
|
17172
|
+
message: MESSAGE$31
|
|
17040
17173
|
});
|
|
17041
17174
|
return;
|
|
17042
17175
|
}
|
|
@@ -17049,7 +17182,7 @@ const noCloneElement = defineRule({
|
|
|
17049
17182
|
if (!isImportedFromModule(node, callee.object.name, "react")) return;
|
|
17050
17183
|
context.report({
|
|
17051
17184
|
node: callee,
|
|
17052
|
-
message: MESSAGE$
|
|
17185
|
+
message: MESSAGE$31
|
|
17053
17186
|
});
|
|
17054
17187
|
}
|
|
17055
17188
|
} })
|
|
@@ -17098,7 +17231,7 @@ const enclosingComponentOrHookName = (node) => {
|
|
|
17098
17231
|
};
|
|
17099
17232
|
//#endregion
|
|
17100
17233
|
//#region src/plugin/rules/state-and-effects/no-create-context-in-render.ts
|
|
17101
|
-
const MESSAGE$
|
|
17234
|
+
const MESSAGE$30 = "createContext() builds a new context every render, so every consumer gets cut off & resets.";
|
|
17102
17235
|
const CONTEXT_MODULES = [
|
|
17103
17236
|
"react",
|
|
17104
17237
|
"use-context-selector",
|
|
@@ -17134,13 +17267,13 @@ const noCreateContextInRender = defineRule({
|
|
|
17134
17267
|
if (!componentOrHookName) return;
|
|
17135
17268
|
context.report({
|
|
17136
17269
|
node,
|
|
17137
|
-
message: `${MESSAGE$
|
|
17270
|
+
message: `${MESSAGE$30} (called inside "${componentOrHookName}")`
|
|
17138
17271
|
});
|
|
17139
17272
|
} })
|
|
17140
17273
|
});
|
|
17141
17274
|
//#endregion
|
|
17142
17275
|
//#region src/plugin/rules/react-builtins/no-create-ref-in-function-component.ts
|
|
17143
|
-
const MESSAGE$
|
|
17276
|
+
const MESSAGE$29 = "`createRef()` in a function component allocates a brand-new ref on every render, so it never holds a value between renders. Use the `useRef()` hook instead.";
|
|
17144
17277
|
const noCreateRefInFunctionComponent = defineRule({
|
|
17145
17278
|
id: "no-create-ref-in-function-component",
|
|
17146
17279
|
title: "createRef in function component",
|
|
@@ -17159,7 +17292,7 @@ const noCreateRefInFunctionComponent = defineRule({
|
|
|
17159
17292
|
if (!(isReactHookName(displayName) || functionContainsReactRenderOutput(enclosingFunction, context.scopes))) return;
|
|
17160
17293
|
context.report({
|
|
17161
17294
|
node,
|
|
17162
|
-
message: MESSAGE$
|
|
17295
|
+
message: MESSAGE$29
|
|
17163
17296
|
});
|
|
17164
17297
|
} })
|
|
17165
17298
|
});
|
|
@@ -17299,12 +17432,13 @@ const noCreateStoreInRender = defineRule({
|
|
|
17299
17432
|
});
|
|
17300
17433
|
//#endregion
|
|
17301
17434
|
//#region src/plugin/rules/react-builtins/no-danger.ts
|
|
17302
|
-
const MESSAGE$
|
|
17435
|
+
const MESSAGE$28 = "`dangerouslySetInnerHTML` is an XSS hole that runs attacker-controlled HTML in your users' browsers.";
|
|
17303
17436
|
const noDanger = defineRule({
|
|
17304
17437
|
id: "no-danger",
|
|
17305
17438
|
title: "Raw HTML injection can run unsafe markup",
|
|
17306
17439
|
severity: "warn",
|
|
17307
17440
|
category: "Security",
|
|
17441
|
+
defaultEnabled: false,
|
|
17308
17442
|
recommendation: "Render trusted content as React children so attacker-controlled HTML cannot run in users' browsers.",
|
|
17309
17443
|
create: (context) => ({
|
|
17310
17444
|
JSXOpeningElement(node) {
|
|
@@ -17312,7 +17446,7 @@ const noDanger = defineRule({
|
|
|
17312
17446
|
if (!propAttribute) return;
|
|
17313
17447
|
context.report({
|
|
17314
17448
|
node: propAttribute.name,
|
|
17315
|
-
message: MESSAGE$
|
|
17449
|
+
message: MESSAGE$28
|
|
17316
17450
|
});
|
|
17317
17451
|
},
|
|
17318
17452
|
CallExpression(node) {
|
|
@@ -17324,7 +17458,7 @@ const noDanger = defineRule({
|
|
|
17324
17458
|
const propertyKey = property.key;
|
|
17325
17459
|
if (isNodeOfType(propertyKey, "Identifier") && propertyKey.name === "dangerouslySetInnerHTML" || isNodeOfType(propertyKey, "Literal") && propertyKey.value === "dangerouslySetInnerHTML") context.report({
|
|
17326
17460
|
node: propertyKey,
|
|
17327
|
-
message: MESSAGE$
|
|
17461
|
+
message: MESSAGE$28
|
|
17328
17462
|
});
|
|
17329
17463
|
}
|
|
17330
17464
|
}
|
|
@@ -17332,7 +17466,7 @@ const noDanger = defineRule({
|
|
|
17332
17466
|
});
|
|
17333
17467
|
//#endregion
|
|
17334
17468
|
//#region src/plugin/rules/react-builtins/no-danger-with-children.ts
|
|
17335
|
-
const MESSAGE$
|
|
17469
|
+
const MESSAGE$27 = "React throws an error when you set both children & `dangerouslySetInnerHTML`.";
|
|
17336
17470
|
const isLineBreak = (child) => {
|
|
17337
17471
|
if (!isNodeOfType(child, "JSXText")) return false;
|
|
17338
17472
|
return child.value.trim().length === 0 && child.value.includes("\n");
|
|
@@ -17402,7 +17536,7 @@ const noDangerWithChildren = defineRule({
|
|
|
17402
17536
|
if (!hasChildrenProp && !hasNestedChildren) return;
|
|
17403
17537
|
if (hasJsxPropIgnoreCase(opening.attributes, "dangerouslySetInnerHTML") || spreadPropsShape.hasDangerously) context.report({
|
|
17404
17538
|
node: opening,
|
|
17405
|
-
message: MESSAGE$
|
|
17539
|
+
message: MESSAGE$27
|
|
17406
17540
|
});
|
|
17407
17541
|
},
|
|
17408
17542
|
CallExpression(node) {
|
|
@@ -17414,7 +17548,7 @@ const noDangerWithChildren = defineRule({
|
|
|
17414
17548
|
if (!propsShape.hasDangerously) return;
|
|
17415
17549
|
if (node.arguments.length >= 3 || propsShape.hasChildren) context.report({
|
|
17416
17550
|
node,
|
|
17417
|
-
message: MESSAGE$
|
|
17551
|
+
message: MESSAGE$27
|
|
17418
17552
|
});
|
|
17419
17553
|
}
|
|
17420
17554
|
})
|
|
@@ -17991,7 +18125,7 @@ const isSetStateCallInLifecycle = (setStateCall, lifecycleNames, options = {}) =
|
|
|
17991
18125
|
//#endregion
|
|
17992
18126
|
//#region src/plugin/rules/react-builtins/no-did-mount-set-state.ts
|
|
17993
18127
|
const LIFECYCLE_NAMES$2 = new Set(["componentDidMount"]);
|
|
17994
|
-
const MESSAGE$
|
|
18128
|
+
const MESSAGE$26 = "Your users see an extra render right after mount when you call `setState` in `componentDidMount`.";
|
|
17995
18129
|
const resolveSettings$20 = (settings) => {
|
|
17996
18130
|
const reactDoctor = settings?.["react-doctor"];
|
|
17997
18131
|
return { mode: (typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.noDidMountSetState ?? {} : {}).mode ?? "allowed" };
|
|
@@ -18010,7 +18144,7 @@ const noDidMountSetState = defineRule({
|
|
|
18010
18144
|
if (!isSetStateCallInLifecycle(node, LIFECYCLE_NAMES$2, { disallowInNestedFunctions: mode === "disallow-in-func" })) return;
|
|
18011
18145
|
context.report({
|
|
18012
18146
|
node: node.callee,
|
|
18013
|
-
message: MESSAGE$
|
|
18147
|
+
message: MESSAGE$26
|
|
18014
18148
|
});
|
|
18015
18149
|
} };
|
|
18016
18150
|
}
|
|
@@ -18018,7 +18152,7 @@ const noDidMountSetState = defineRule({
|
|
|
18018
18152
|
//#endregion
|
|
18019
18153
|
//#region src/plugin/rules/react-builtins/no-did-update-set-state.ts
|
|
18020
18154
|
const LIFECYCLE_NAMES$1 = new Set(["componentDidUpdate"]);
|
|
18021
|
-
const MESSAGE$
|
|
18155
|
+
const MESSAGE$25 = "Calling setState in componentDidUpdate can trigger another update immediately, loop forever, and freeze the component.";
|
|
18022
18156
|
const resolveSettings$19 = (settings) => {
|
|
18023
18157
|
const reactDoctor = settings?.["react-doctor"];
|
|
18024
18158
|
return { mode: (typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.noDidUpdateSetState ?? {} : {}).mode ?? "allowed" };
|
|
@@ -18037,7 +18171,7 @@ const noDidUpdateSetState = defineRule({
|
|
|
18037
18171
|
if (!isSetStateCallInLifecycle(node, LIFECYCLE_NAMES$1, { disallowInNestedFunctions: mode === "disallow-in-func" })) return;
|
|
18038
18172
|
context.report({
|
|
18039
18173
|
node: node.callee,
|
|
18040
|
-
message: MESSAGE$
|
|
18174
|
+
message: MESSAGE$25
|
|
18041
18175
|
});
|
|
18042
18176
|
} };
|
|
18043
18177
|
}
|
|
@@ -18060,7 +18194,7 @@ const isStateMemberExpression = (node) => {
|
|
|
18060
18194
|
};
|
|
18061
18195
|
//#endregion
|
|
18062
18196
|
//#region src/plugin/rules/react-builtins/no-direct-mutation-state.ts
|
|
18063
|
-
const MESSAGE$
|
|
18197
|
+
const MESSAGE$24 = "Your users see stale data because mutating `this.state` by hand never redraws & gets overwritten.";
|
|
18064
18198
|
const shouldIgnoreMutation = (node) => {
|
|
18065
18199
|
let isConstructor = false;
|
|
18066
18200
|
let isInsideCallExpression = false;
|
|
@@ -18082,7 +18216,7 @@ const reportIfStateMutation = (context, reportNode, target) => {
|
|
|
18082
18216
|
if (shouldIgnoreMutation(reportNode)) return;
|
|
18083
18217
|
context.report({
|
|
18084
18218
|
node: reportNode,
|
|
18085
|
-
message: MESSAGE$
|
|
18219
|
+
message: MESSAGE$24
|
|
18086
18220
|
});
|
|
18087
18221
|
};
|
|
18088
18222
|
const noDirectMutationState = defineRule({
|
|
@@ -18292,6 +18426,26 @@ const noDocumentStartViewTransition = defineRule({
|
|
|
18292
18426
|
} })
|
|
18293
18427
|
});
|
|
18294
18428
|
//#endregion
|
|
18429
|
+
//#region src/plugin/rules/js-performance/no-document-write.ts
|
|
18430
|
+
const MESSAGE$23 = "`document.write()` blocks parsing, is ignored (or wipes the page) after load, and is flagged by browsers as a performance anti-pattern. Build DOM nodes or set `innerHTML`/`textContent` on a target element instead.";
|
|
18431
|
+
const WRITE_METHODS = new Set(["write", "writeln"]);
|
|
18432
|
+
const noDocumentWrite = defineRule({
|
|
18433
|
+
id: "no-document-write",
|
|
18434
|
+
title: "document.write/writeln",
|
|
18435
|
+
severity: "warn",
|
|
18436
|
+
recommendation: "Don't use `document.write()`/`document.writeln()`. Append DOM nodes or set `innerHTML`/`textContent` on a specific element instead.",
|
|
18437
|
+
create: (context) => ({ CallExpression(node) {
|
|
18438
|
+
const callee = node.callee;
|
|
18439
|
+
if (!isNodeOfType(callee, "MemberExpression") || callee.computed) return;
|
|
18440
|
+
if (!isNodeOfType(callee.object, "Identifier") || callee.object.name !== "document") return;
|
|
18441
|
+
if (!isNodeOfType(callee.property, "Identifier") || !WRITE_METHODS.has(callee.property.name)) return;
|
|
18442
|
+
context.report({
|
|
18443
|
+
node,
|
|
18444
|
+
message: MESSAGE$23
|
|
18445
|
+
});
|
|
18446
|
+
} })
|
|
18447
|
+
});
|
|
18448
|
+
//#endregion
|
|
18295
18449
|
//#region src/plugin/rules/bundle-size/no-dynamic-import-path.ts
|
|
18296
18450
|
const noDynamicImportPath = defineRule({
|
|
18297
18451
|
id: "no-dynamic-import-path",
|
|
@@ -19670,7 +19824,7 @@ const ALLOWED_NAMESPACES = new Set([
|
|
|
19670
19824
|
"ReactDOM",
|
|
19671
19825
|
"ReactDom"
|
|
19672
19826
|
]);
|
|
19673
|
-
const MESSAGE$
|
|
19827
|
+
const MESSAGE$22 = "`findDOMNode` crashes your app in React 19 because it was removed.";
|
|
19674
19828
|
const noFindDomNode = defineRule({
|
|
19675
19829
|
id: "no-find-dom-node",
|
|
19676
19830
|
title: "findDOMNode breaks component encapsulation",
|
|
@@ -19681,7 +19835,7 @@ const noFindDomNode = defineRule({
|
|
|
19681
19835
|
if (isNodeOfType(callee, "Identifier") && callee.name === "findDOMNode") {
|
|
19682
19836
|
context.report({
|
|
19683
19837
|
node: callee,
|
|
19684
|
-
message: MESSAGE$
|
|
19838
|
+
message: MESSAGE$22
|
|
19685
19839
|
});
|
|
19686
19840
|
return;
|
|
19687
19841
|
}
|
|
@@ -19692,7 +19846,7 @@ const noFindDomNode = defineRule({
|
|
|
19692
19846
|
if (callee.property.name !== "findDOMNode") return;
|
|
19693
19847
|
context.report({
|
|
19694
19848
|
node: callee.property,
|
|
19695
|
-
message: MESSAGE$
|
|
19849
|
+
message: MESSAGE$22
|
|
19696
19850
|
});
|
|
19697
19851
|
}
|
|
19698
19852
|
} })
|
|
@@ -19934,7 +20088,7 @@ const noGrayOnColoredBackground = defineRule({
|
|
|
19934
20088
|
});
|
|
19935
20089
|
//#endregion
|
|
19936
20090
|
//#region src/plugin/rules/performance/no-img-lazy-with-high-fetchpriority.ts
|
|
19937
|
-
const MESSAGE$
|
|
20091
|
+
const MESSAGE$21 = "`<img loading=\"lazy\">` defers the request while `fetchPriority=\"high\"` asks the browser to rush it, so the two directives contradict each other. Drop one: keep `fetchPriority=\"high\"` (and eager loading) for an LCP image, or `loading=\"lazy\"` for a below-the-fold one.";
|
|
19938
20092
|
const noImgLazyWithHighFetchpriority = defineRule({
|
|
19939
20093
|
id: "no-img-lazy-with-high-fetchpriority",
|
|
19940
20094
|
title: "Lazy image with high fetchPriority",
|
|
@@ -19948,7 +20102,7 @@ const noImgLazyWithHighFetchpriority = defineRule({
|
|
|
19948
20102
|
if (!fetchPriorityAttribute || getJsxPropStringValue(fetchPriorityAttribute)?.toLowerCase() !== "high") return;
|
|
19949
20103
|
context.report({
|
|
19950
20104
|
node: node.name,
|
|
19951
|
-
message: MESSAGE$
|
|
20105
|
+
message: MESSAGE$21
|
|
19952
20106
|
});
|
|
19953
20107
|
} })
|
|
19954
20108
|
});
|
|
@@ -20045,15 +20199,20 @@ const noInlineExhaustiveStyle = defineRule({
|
|
|
20045
20199
|
severity: "warn",
|
|
20046
20200
|
tags: ["test-noise", "react-jsx-only"],
|
|
20047
20201
|
recommendation: "Move the styles to a CSS class, CSS module, Tailwind utilities, or a styled component. Big inline objects are hard to read and rebuild on every update.",
|
|
20048
|
-
create: (context) =>
|
|
20049
|
-
|
|
20050
|
-
|
|
20051
|
-
|
|
20052
|
-
|
|
20053
|
-
|
|
20054
|
-
|
|
20055
|
-
|
|
20056
|
-
|
|
20202
|
+
create: (context) => {
|
|
20203
|
+
if (isGeneratedImageRenderContext(context)) return {};
|
|
20204
|
+
return { JSXAttribute(node) {
|
|
20205
|
+
const expression = getInlineStyleExpression(node);
|
|
20206
|
+
if (!expression) return;
|
|
20207
|
+
const propertyCount = expression.properties?.filter((property) => isNodeOfType(property, "Property")).length ?? 0;
|
|
20208
|
+
if (propertyCount < 8) return;
|
|
20209
|
+
if (isGeneratedImageRenderContext(context, node.parent ?? void 0)) return;
|
|
20210
|
+
context.report({
|
|
20211
|
+
node: expression,
|
|
20212
|
+
message: `This inline style has ${propertyCount} properties, which is hard to read & rebuilds every render. Move it to a CSS class, CSS module, or styled component.`
|
|
20213
|
+
});
|
|
20214
|
+
} };
|
|
20215
|
+
}
|
|
20057
20216
|
});
|
|
20058
20217
|
//#endregion
|
|
20059
20218
|
//#region src/plugin/rules/performance/no-inline-prop-on-memo-component.ts
|
|
@@ -20183,7 +20342,7 @@ const noIsMounted = defineRule({
|
|
|
20183
20342
|
});
|
|
20184
20343
|
//#endregion
|
|
20185
20344
|
//#region src/plugin/rules/js-performance/no-json-parse-stringify-clone.ts
|
|
20186
|
-
const MESSAGE$
|
|
20345
|
+
const MESSAGE$20 = "`JSON.parse(JSON.stringify(x))` deep-clones by re-serializing: it is slow on large objects and silently drops `undefined`, functions, `Date`/`Map`/`Set`, and cyclic references. Use `structuredClone(x)`.";
|
|
20187
20346
|
const isJsonMethodCall = (node, method) => {
|
|
20188
20347
|
if (!isNodeOfType(node, "CallExpression")) return false;
|
|
20189
20348
|
const callee = node.callee;
|
|
@@ -20200,13 +20359,13 @@ const noJsonParseStringifyClone = defineRule({
|
|
|
20200
20359
|
if (!firstArgument || !isJsonMethodCall(firstArgument, "stringify")) return;
|
|
20201
20360
|
context.report({
|
|
20202
20361
|
node,
|
|
20203
|
-
message: MESSAGE$
|
|
20362
|
+
message: MESSAGE$20
|
|
20204
20363
|
});
|
|
20205
20364
|
} })
|
|
20206
20365
|
});
|
|
20207
20366
|
//#endregion
|
|
20208
20367
|
//#region src/plugin/rules/correctness/no-jsx-element-type.ts
|
|
20209
|
-
const MESSAGE$
|
|
20368
|
+
const MESSAGE$19 = "`JSX.Element` is too narrow: it excludes `null`, strings, numbers, and fragments that components commonly return. Use `React.ReactNode` instead.";
|
|
20210
20369
|
const isJsxElementTypeReference = (node) => {
|
|
20211
20370
|
if (!isNodeOfType(node, "TSTypeReference")) return false;
|
|
20212
20371
|
const typeName = node.typeName;
|
|
@@ -20223,7 +20382,7 @@ const checkReturnType = (context, returnType) => {
|
|
|
20223
20382
|
if (!typeAnnotation) return;
|
|
20224
20383
|
if (isJsxElementTypeReference(typeAnnotation)) context.report({
|
|
20225
20384
|
node: typeAnnotation,
|
|
20226
|
-
message: MESSAGE$
|
|
20385
|
+
message: MESSAGE$19
|
|
20227
20386
|
});
|
|
20228
20387
|
};
|
|
20229
20388
|
const noJsxElementType = defineRule({
|
|
@@ -20678,7 +20837,7 @@ const noMoment = defineRule({
|
|
|
20678
20837
|
});
|
|
20679
20838
|
//#endregion
|
|
20680
20839
|
//#region src/plugin/rules/react-builtins/no-multi-comp.ts
|
|
20681
|
-
const MESSAGE$
|
|
20840
|
+
const MESSAGE$18 = "This file declares several components, so each component is harder to find, test, and change.";
|
|
20682
20841
|
const resolveSettings$16 = (settings) => {
|
|
20683
20842
|
const reactDoctor = settings?.["react-doctor"];
|
|
20684
20843
|
return { ignoreStateless: (typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.noMultiComp ?? {} : {}).ignoreStateless ?? false };
|
|
@@ -21000,7 +21159,7 @@ const noMultiComp = defineRule({
|
|
|
21000
21159
|
if (isSmallFeatureModule || isLargeFeatureModule || isVeryLargeFeatureModule) return;
|
|
21001
21160
|
for (const component of flagged.slice(1)) context.report({
|
|
21002
21161
|
node: component.reportNode,
|
|
21003
|
-
message: MESSAGE$
|
|
21162
|
+
message: MESSAGE$18
|
|
21004
21163
|
});
|
|
21005
21164
|
} };
|
|
21006
21165
|
}
|
|
@@ -21168,7 +21327,7 @@ const resolveReducerFunction = (node, currentFilename) => {
|
|
|
21168
21327
|
};
|
|
21169
21328
|
//#endregion
|
|
21170
21329
|
//#region src/plugin/rules/state-and-effects/no-mutating-reducer-state.ts
|
|
21171
|
-
const MESSAGE$
|
|
21330
|
+
const MESSAGE$17 = "This reducer changes state in place, so your update is silently skipped.";
|
|
21172
21331
|
const SAME_REFERENCE_ARRAY_RETURN_METHODS = new Set([
|
|
21173
21332
|
"copyWithin",
|
|
21174
21333
|
"fill",
|
|
@@ -21378,7 +21537,7 @@ const analyzeReactUseReducerFunctionForStateMutation = (context, functionNode, r
|
|
|
21378
21537
|
reportedNodes.add(options.crossFileConsumerCallSite);
|
|
21379
21538
|
context.report({
|
|
21380
21539
|
node: options.crossFileConsumerCallSite,
|
|
21381
|
-
message: `${MESSAGE$
|
|
21540
|
+
message: `${MESSAGE$17} (mutation in imported reducer at \`${options.crossFileSourceDisplay}\`)`
|
|
21382
21541
|
});
|
|
21383
21542
|
return;
|
|
21384
21543
|
}
|
|
@@ -21387,7 +21546,7 @@ const analyzeReactUseReducerFunctionForStateMutation = (context, functionNode, r
|
|
|
21387
21546
|
reportedNodes.add(mutation.node);
|
|
21388
21547
|
context.report({
|
|
21389
21548
|
node: mutation.node,
|
|
21390
|
-
message: MESSAGE$
|
|
21549
|
+
message: MESSAGE$17
|
|
21391
21550
|
});
|
|
21392
21551
|
}
|
|
21393
21552
|
};
|
|
@@ -21659,7 +21818,7 @@ const noNoninteractiveElementToInteractiveRole = defineRule({
|
|
|
21659
21818
|
});
|
|
21660
21819
|
//#endregion
|
|
21661
21820
|
//#region src/plugin/rules/a11y/no-noninteractive-tabindex.ts
|
|
21662
|
-
const MESSAGE$
|
|
21821
|
+
const MESSAGE$16 = "Keyboard users get stuck focusing this element they can't act on because `tabIndex` makes it tabbable, so remove it.";
|
|
21663
21822
|
const resolveSettings$14 = (settings) => {
|
|
21664
21823
|
const reactDoctor = settings?.["react-doctor"];
|
|
21665
21824
|
const ruleSettings = typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.noNoninteractiveTabindex ?? {} : {};
|
|
@@ -21687,7 +21846,7 @@ const noNoninteractiveTabindex = defineRule({
|
|
|
21687
21846
|
if (numeric === null) {
|
|
21688
21847
|
if (isNodeOfType(tabIndexValue, "JSXExpressionContainer") && !settings.allowExpressionValues) context.report({
|
|
21689
21848
|
node: tabIndex,
|
|
21690
|
-
message: MESSAGE$
|
|
21849
|
+
message: MESSAGE$16
|
|
21691
21850
|
});
|
|
21692
21851
|
return;
|
|
21693
21852
|
}
|
|
@@ -21700,7 +21859,7 @@ const noNoninteractiveTabindex = defineRule({
|
|
|
21700
21859
|
if (!roleAttribute) {
|
|
21701
21860
|
context.report({
|
|
21702
21861
|
node: tabIndex,
|
|
21703
|
-
message: MESSAGE$
|
|
21862
|
+
message: MESSAGE$16
|
|
21704
21863
|
});
|
|
21705
21864
|
return;
|
|
21706
21865
|
}
|
|
@@ -21714,7 +21873,7 @@ const noNoninteractiveTabindex = defineRule({
|
|
|
21714
21873
|
}
|
|
21715
21874
|
context.report({
|
|
21716
21875
|
node: tabIndex,
|
|
21717
|
-
message: MESSAGE$
|
|
21876
|
+
message: MESSAGE$16
|
|
21718
21877
|
});
|
|
21719
21878
|
} };
|
|
21720
21879
|
}
|
|
@@ -22405,7 +22564,7 @@ const noRandomKey = defineRule({
|
|
|
22405
22564
|
});
|
|
22406
22565
|
//#endregion
|
|
22407
22566
|
//#region src/plugin/rules/react-builtins/no-react-children.ts
|
|
22408
|
-
const MESSAGE$
|
|
22567
|
+
const MESSAGE$15 = "`React.Children` traversal depends on the runtime child shape, so wrapping or unwrapping a child can silently change what gets visited.";
|
|
22409
22568
|
const isChildrenIdentifier = (node, contextNode) => {
|
|
22410
22569
|
if (!isNodeOfType(node, "Identifier") || node.name !== "Children") return false;
|
|
22411
22570
|
return isImportedFromModule(contextNode, "Children", "react");
|
|
@@ -22431,13 +22590,13 @@ const noReactChildren = defineRule({
|
|
|
22431
22590
|
if (isChildrenIdentifier(memberObject, node)) {
|
|
22432
22591
|
context.report({
|
|
22433
22592
|
node: calleeOuter,
|
|
22434
|
-
message: MESSAGE$
|
|
22593
|
+
message: MESSAGE$15
|
|
22435
22594
|
});
|
|
22436
22595
|
return;
|
|
22437
22596
|
}
|
|
22438
22597
|
if (isReactNamespaceMember(memberObject, node)) context.report({
|
|
22439
22598
|
node: calleeOuter,
|
|
22440
|
-
message: MESSAGE$
|
|
22599
|
+
message: MESSAGE$15
|
|
22441
22600
|
});
|
|
22442
22601
|
} })
|
|
22443
22602
|
});
|
|
@@ -22760,7 +22919,7 @@ const noRenderPropChildren = defineRule({
|
|
|
22760
22919
|
});
|
|
22761
22920
|
//#endregion
|
|
22762
22921
|
//#region src/plugin/rules/react-builtins/no-render-return-value.ts
|
|
22763
|
-
const MESSAGE$
|
|
22922
|
+
const MESSAGE$14 = "Your app breaks in React 19 because `ReactDOM.render` returns nothing there.";
|
|
22764
22923
|
const isReactDomRenderCall = (node) => {
|
|
22765
22924
|
if (!isNodeOfType(node.callee, "MemberExpression")) return false;
|
|
22766
22925
|
if (!isNodeOfType(node.callee.object, "Identifier")) return false;
|
|
@@ -22784,7 +22943,7 @@ const noRenderReturnValue = defineRule({
|
|
|
22784
22943
|
if (!isUsedAsReturnValue(node.parent)) return;
|
|
22785
22944
|
context.report({
|
|
22786
22945
|
node: node.callee,
|
|
22787
|
-
message: MESSAGE$
|
|
22946
|
+
message: MESSAGE$14
|
|
22788
22947
|
});
|
|
22789
22948
|
} })
|
|
22790
22949
|
});
|
|
@@ -22944,11 +23103,17 @@ const classifySecretFileExposure = (filename, options = {}) => {
|
|
|
22944
23103
|
return "unknown";
|
|
22945
23104
|
};
|
|
22946
23105
|
//#endregion
|
|
22947
|
-
//#region src/plugin/utils/
|
|
22948
|
-
const
|
|
22949
|
-
|
|
23106
|
+
//#region src/plugin/utils/tokenize-identifier-words.ts
|
|
23107
|
+
const IDENTIFIER_WORD_PATTERN = /[A-Z]+(?=[A-Z][a-z]|\b)|[A-Z]?[a-z]+|\d+/g;
|
|
23108
|
+
const tokenizeIdentifierWords = (identifierName) => {
|
|
23109
|
+
const words = identifierName.match(IDENTIFIER_WORD_PATTERN);
|
|
23110
|
+
if (!words) return [];
|
|
23111
|
+
return words.map((word) => word.toLowerCase());
|
|
22950
23112
|
};
|
|
22951
23113
|
//#endregion
|
|
23114
|
+
//#region src/plugin/utils/get-identifier-trailing-word.ts
|
|
23115
|
+
const getIdentifierTrailingWord = (identifierName) => tokenizeIdentifierWords(identifierName).at(-1) ?? identifierName.toLowerCase();
|
|
23116
|
+
//#endregion
|
|
22952
23117
|
//#region src/plugin/constants/tanstack.ts
|
|
22953
23118
|
const TANSTACK_ROUTE_FILE_PATTERN = /\/routes\//;
|
|
22954
23119
|
const TANSTACK_ROOT_ROUTE_FILE_PATTERN = /__root\.(tsx?|jsx?)$/;
|
|
@@ -23476,7 +23641,7 @@ const getParentComponent = (node) => {
|
|
|
23476
23641
|
};
|
|
23477
23642
|
//#endregion
|
|
23478
23643
|
//#region src/plugin/rules/react-builtins/no-set-state.ts
|
|
23479
|
-
const MESSAGE$
|
|
23644
|
+
const MESSAGE$13 = "`this.setState` keeps local class state in a project that forbids it, so state ownership becomes harder to reason about.";
|
|
23480
23645
|
const noSetState = defineRule({
|
|
23481
23646
|
id: "no-set-state",
|
|
23482
23647
|
title: "Local class state forbidden",
|
|
@@ -23491,7 +23656,7 @@ const noSetState = defineRule({
|
|
|
23491
23656
|
if (!getParentComponent(node)) return;
|
|
23492
23657
|
context.report({
|
|
23493
23658
|
node: node.callee,
|
|
23494
|
-
message: MESSAGE$
|
|
23659
|
+
message: MESSAGE$13
|
|
23495
23660
|
});
|
|
23496
23661
|
} })
|
|
23497
23662
|
});
|
|
@@ -23653,7 +23818,7 @@ const isAbstractRole = (openingElement, settings) => {
|
|
|
23653
23818
|
};
|
|
23654
23819
|
//#endregion
|
|
23655
23820
|
//#region src/plugin/rules/a11y/no-static-element-interactions.ts
|
|
23656
|
-
const MESSAGE$
|
|
23821
|
+
const MESSAGE$12 = "Screen reader users can't tell this click handler is interactive because it has no `role`, so add a `role` or use a button or link.";
|
|
23657
23822
|
const DEFAULT_HANDLERS = [
|
|
23658
23823
|
"onClick",
|
|
23659
23824
|
"onMouseDown",
|
|
@@ -23713,7 +23878,7 @@ const noStaticElementInteractions = defineRule({
|
|
|
23713
23878
|
if (!roleAttribute || !roleAttribute.value) {
|
|
23714
23879
|
context.report({
|
|
23715
23880
|
node: node.name,
|
|
23716
|
-
message: MESSAGE$
|
|
23881
|
+
message: MESSAGE$12
|
|
23717
23882
|
});
|
|
23718
23883
|
return;
|
|
23719
23884
|
}
|
|
@@ -23723,19 +23888,66 @@ const noStaticElementInteractions = defineRule({
|
|
|
23723
23888
|
if (firstRole && (isInteractiveRole(firstRole) || isNonInteractiveRole(firstRole))) return;
|
|
23724
23889
|
context.report({
|
|
23725
23890
|
node: node.name,
|
|
23726
|
-
message: MESSAGE$
|
|
23891
|
+
message: MESSAGE$12
|
|
23727
23892
|
});
|
|
23728
23893
|
return;
|
|
23729
23894
|
}
|
|
23730
23895
|
if (isNodeOfType(attributeValue, "JSXExpressionContainer") && settings.allowExpressionValues) return;
|
|
23731
23896
|
context.report({
|
|
23732
23897
|
node: node.name,
|
|
23733
|
-
message: MESSAGE$
|
|
23898
|
+
message: MESSAGE$12
|
|
23734
23899
|
});
|
|
23735
23900
|
} };
|
|
23736
23901
|
}
|
|
23737
23902
|
});
|
|
23738
23903
|
//#endregion
|
|
23904
|
+
//#region src/plugin/rules/react-builtins/no-string-false-on-boolean-attribute.ts
|
|
23905
|
+
const BOOLEAN_ATTRIBUTES = new Set([
|
|
23906
|
+
"disabled",
|
|
23907
|
+
"checked",
|
|
23908
|
+
"readonly",
|
|
23909
|
+
"required",
|
|
23910
|
+
"selected",
|
|
23911
|
+
"multiple",
|
|
23912
|
+
"autofocus",
|
|
23913
|
+
"autoplay",
|
|
23914
|
+
"controls",
|
|
23915
|
+
"loop",
|
|
23916
|
+
"muted",
|
|
23917
|
+
"open",
|
|
23918
|
+
"reversed",
|
|
23919
|
+
"default",
|
|
23920
|
+
"novalidate",
|
|
23921
|
+
"formnovalidate",
|
|
23922
|
+
"playsinline",
|
|
23923
|
+
"itemscope",
|
|
23924
|
+
"allowfullscreen"
|
|
23925
|
+
]);
|
|
23926
|
+
const noStringFalseOnBooleanAttribute = defineRule({
|
|
23927
|
+
id: "no-string-false-on-boolean-attribute",
|
|
23928
|
+
title: "String true/false on a boolean attribute",
|
|
23929
|
+
severity: "warn",
|
|
23930
|
+
recommendation: "Use the boolean form on boolean attributes: `disabled` / `disabled={true}` / `disabled={false}`, not `disabled=\"false\"`. A non-empty string is truthy, so `=\"false\"` actually turns the attribute ON.",
|
|
23931
|
+
create: (context) => ({ JSXOpeningElement(node) {
|
|
23932
|
+
if (!isNodeOfType(node.name, "JSXIdentifier")) return;
|
|
23933
|
+
const firstCharacter = node.name.name.charCodeAt(0);
|
|
23934
|
+
if (firstCharacter < 97 || firstCharacter > 122) return;
|
|
23935
|
+
for (const attribute of node.attributes) {
|
|
23936
|
+
if (!isNodeOfType(attribute, "JSXAttribute")) continue;
|
|
23937
|
+
if (!isNodeOfType(attribute.name, "JSXIdentifier")) continue;
|
|
23938
|
+
if (!BOOLEAN_ATTRIBUTES.has(attribute.name.name.toLowerCase())) continue;
|
|
23939
|
+
const value = getJsxPropStringValue(attribute);
|
|
23940
|
+
if (value !== "false" && value !== "true") continue;
|
|
23941
|
+
const attributeName = attribute.name.name;
|
|
23942
|
+
const guidance = value === "false" ? `which React treats as truthy, so the attribute is applied even though you wrote "false". Use \`${attributeName}={false}\` (or omit the attribute) to keep it off` : `but a boolean attribute takes a boolean, not the string "true". Use \`${attributeName}\` or \`${attributeName}={true}\``;
|
|
23943
|
+
context.report({
|
|
23944
|
+
node: attribute,
|
|
23945
|
+
message: `\`${attributeName}="${value}"\` passes the string "${value}", ${guidance}.`
|
|
23946
|
+
});
|
|
23947
|
+
}
|
|
23948
|
+
} })
|
|
23949
|
+
});
|
|
23950
|
+
//#endregion
|
|
23739
23951
|
//#region src/plugin/rules/react-builtins/no-string-refs.ts
|
|
23740
23952
|
const STRING_IN_REF_MESSAGE = "Your component can't reach this node because string refs don't work in modern React.";
|
|
23741
23953
|
const THIS_REFS_MESSAGE = "Your component can't reach its nodes because `this.refs` is empty in modern React.";
|
|
@@ -23786,6 +23998,27 @@ const noStringRefs = defineRule({
|
|
|
23786
23998
|
}
|
|
23787
23999
|
});
|
|
23788
24000
|
//#endregion
|
|
24001
|
+
//#region src/plugin/rules/js-performance/no-sync-xhr.ts
|
|
24002
|
+
const MESSAGE$11 = "A synchronous `XMLHttpRequest` (`.open(method, url, false)`) freezes the main thread until the request finishes, blocking all rendering and input. Use `fetch()` or an async XHR (`open(method, url, true)`).";
|
|
24003
|
+
const isFalseLiteral = (node) => isNodeOfType(node, "Literal") && node.value === false;
|
|
24004
|
+
const noSyncXhr = defineRule({
|
|
24005
|
+
id: "no-sync-xhr",
|
|
24006
|
+
title: "Synchronous XMLHttpRequest",
|
|
24007
|
+
severity: "warn",
|
|
24008
|
+
recommendation: "Never open an XMLHttpRequest synchronously (`async` = `false`). It blocks the main thread. Use `fetch()` or pass `true` and handle the response asynchronously.",
|
|
24009
|
+
create: (context) => ({ CallExpression(node) {
|
|
24010
|
+
const callee = node.callee;
|
|
24011
|
+
if (!isNodeOfType(callee, "MemberExpression") || callee.computed) return;
|
|
24012
|
+
if (!isNodeOfType(callee.property, "Identifier") || callee.property.name !== "open") return;
|
|
24013
|
+
const asyncArgument = node.arguments?.[2];
|
|
24014
|
+
if (!asyncArgument || !isFalseLiteral(stripParenExpression(asyncArgument))) return;
|
|
24015
|
+
context.report({
|
|
24016
|
+
node,
|
|
24017
|
+
message: MESSAGE$11
|
|
24018
|
+
});
|
|
24019
|
+
} })
|
|
24020
|
+
});
|
|
24021
|
+
//#endregion
|
|
23789
24022
|
//#region src/plugin/rules/react-builtins/no-this-in-sfc.ts
|
|
23790
24023
|
const MESSAGE$10 = "This value is `undefined` because function components have no `this`.";
|
|
23791
24024
|
const isInsideClassMethod = (node, customClassFactoryNames) => {
|
|
@@ -25195,15 +25428,8 @@ const expressionContainsJsxOrCreateElement = (root) => {
|
|
|
25195
25428
|
visit(root);
|
|
25196
25429
|
return found;
|
|
25197
25430
|
};
|
|
25198
|
-
const classExtendsReactComponent$1 = (classNode) => {
|
|
25199
|
-
const superClass = classNode.superClass;
|
|
25200
|
-
if (!superClass) return false;
|
|
25201
|
-
if (isNodeOfType(superClass, "Identifier") && (superClass.name === "Component" || superClass.name === "PureComponent")) return true;
|
|
25202
|
-
if (isNodeOfType(superClass, "MemberExpression") && isNodeOfType(superClass.object, "Identifier") && superClass.object.name === "React" && isNodeOfType(superClass.property, "Identifier") && (superClass.property.name === "Component" || superClass.property.name === "PureComponent")) return true;
|
|
25203
|
-
return false;
|
|
25204
|
-
};
|
|
25205
25431
|
const isReactClassComponent = (classNode) => {
|
|
25206
|
-
if (
|
|
25432
|
+
if (isEs6Component(classNode)) return true;
|
|
25207
25433
|
return expressionContainsJsxOrCreateElement(classNode);
|
|
25208
25434
|
};
|
|
25209
25435
|
const findEnclosingComponent = (node) => {
|
|
@@ -25363,7 +25589,7 @@ const noUnstableNestedComponents = defineRule({
|
|
|
25363
25589
|
create: (context) => {
|
|
25364
25590
|
const settings = resolveSettings$8(context.settings);
|
|
25365
25591
|
const renderPropRegex = compileGlob(settings.propNamePattern);
|
|
25366
|
-
const reportCandidate = (candidateNode, reportNode
|
|
25592
|
+
const reportCandidate = (candidateNode, reportNode) => {
|
|
25367
25593
|
if (isFirstArgumentOfHocCall(candidateNode)) return;
|
|
25368
25594
|
if (isReturnOfMapCallback(candidateNode)) return;
|
|
25369
25595
|
const propInfo = isComponentDeclaredInProp(candidateNode);
|
|
@@ -25384,7 +25610,7 @@ const noUnstableNestedComponents = defineRule({
|
|
|
25384
25610
|
const inferredName = inferFunctionLikeName(node);
|
|
25385
25611
|
const propInfo = isComponentDeclaredInProp(node);
|
|
25386
25612
|
if (!(inferredName !== null && isReactComponentName(inferredName) || propInfo !== null || isObjectCallbackCandidate(node))) return;
|
|
25387
|
-
reportCandidate(node, node
|
|
25613
|
+
reportCandidate(node, node);
|
|
25388
25614
|
};
|
|
25389
25615
|
return {
|
|
25390
25616
|
FunctionDeclaration: checkFunctionLike,
|
|
@@ -25394,18 +25620,18 @@ const noUnstableNestedComponents = defineRule({
|
|
|
25394
25620
|
if (!node.id) return;
|
|
25395
25621
|
if (!isReactComponentName(node.id.name)) return;
|
|
25396
25622
|
if (!isReactClassComponent(node)) return;
|
|
25397
|
-
reportCandidate(node, node
|
|
25623
|
+
reportCandidate(node, node);
|
|
25398
25624
|
},
|
|
25399
25625
|
ClassExpression(node) {
|
|
25400
25626
|
const inferredName = node.id?.name ?? inferFunctionLikeName(node);
|
|
25401
25627
|
if (!inferredName || !isReactComponentName(inferredName)) return;
|
|
25402
25628
|
if (!isReactClassComponent(node)) return;
|
|
25403
|
-
reportCandidate(node, node
|
|
25629
|
+
reportCandidate(node, node);
|
|
25404
25630
|
},
|
|
25405
25631
|
CallExpression(node) {
|
|
25406
25632
|
if (!isHocCallee$1(node)) return;
|
|
25407
25633
|
if (!hocCallContainsComponent(node)) return;
|
|
25408
|
-
reportCandidate(node, node
|
|
25634
|
+
reportCandidate(node, node);
|
|
25409
25635
|
}
|
|
25410
25636
|
};
|
|
25411
25637
|
}
|
|
@@ -25845,13 +26071,6 @@ const skipTsExpression = (expression) => {
|
|
|
25845
26071
|
if (expression.type === "TSAsExpression" || expression.type === "TSSatisfiesExpression" || expression.type === "TSNonNullExpression") return skipTsExpression(expression.expression);
|
|
25846
26072
|
return expression;
|
|
25847
26073
|
};
|
|
25848
|
-
const classExtendsReactComponent = (classNode) => {
|
|
25849
|
-
const superClass = classNode.superClass;
|
|
25850
|
-
if (!superClass) return false;
|
|
25851
|
-
if (isNodeOfType(superClass, "Identifier") && (superClass.name === "Component" || superClass.name === "PureComponent")) return true;
|
|
25852
|
-
if (isNodeOfType(superClass, "MemberExpression") && isNodeOfType(superClass.object, "Identifier") && superClass.object.name === "React" && isNodeOfType(superClass.property, "Identifier") && (superClass.property.name === "Component" || superClass.property.name === "PureComponent")) return true;
|
|
25853
|
-
return false;
|
|
25854
|
-
};
|
|
25855
26074
|
const isReactCreateContext = (initializer) => {
|
|
25856
26075
|
if (!initializer) return false;
|
|
25857
26076
|
const expression = skipTsExpression(initializer);
|
|
@@ -26042,7 +26261,7 @@ const onlyExportComponents = defineRule({
|
|
|
26042
26261
|
if (stripped.id) {
|
|
26043
26262
|
const idNode = stripped.id;
|
|
26044
26263
|
isExportedNodeIds.add(stripped);
|
|
26045
|
-
if (isReactComponentName(idNode.name) &&
|
|
26264
|
+
if (isReactComponentName(idNode.name) && isEs6Component(stripped)) hasReactExport = true;
|
|
26046
26265
|
else exports.push({
|
|
26047
26266
|
kind: "non-component",
|
|
26048
26267
|
reportNode: idNode
|
|
@@ -26102,7 +26321,7 @@ const onlyExportComponents = defineRule({
|
|
|
26102
26321
|
exports.push(classifyExport(declaration.id.name, declaration.id, true, null, state));
|
|
26103
26322
|
} else if (isNodeOfType(declaration, "ClassDeclaration") && declaration.id) {
|
|
26104
26323
|
isExportedNodeIds.add(declaration);
|
|
26105
|
-
if (isReactComponentName(declaration.id.name) &&
|
|
26324
|
+
if (isReactComponentName(declaration.id.name) && isEs6Component(declaration)) exports.push({ kind: "react-component" });
|
|
26106
26325
|
else exports.push({
|
|
26107
26326
|
kind: "non-component",
|
|
26108
26327
|
reportNode: declaration.id
|
|
@@ -34895,6 +35114,47 @@ const serverAfterNonblocking = defineRule({
|
|
|
34895
35114
|
}
|
|
34896
35115
|
});
|
|
34897
35116
|
//#endregion
|
|
35117
|
+
//#region src/plugin/utils/is-auth-guard-name.ts
|
|
35118
|
+
const SIGNED_IN_HEAD_TOKENS = new Set([
|
|
35119
|
+
"signed",
|
|
35120
|
+
"logged",
|
|
35121
|
+
"sign"
|
|
35122
|
+
]);
|
|
35123
|
+
const mergeSignedInTokens = (tokens) => {
|
|
35124
|
+
const mergedTokens = [];
|
|
35125
|
+
for (let tokenIndex = 0; tokenIndex < tokens.length; tokenIndex += 1) {
|
|
35126
|
+
const currentToken = tokens[tokenIndex];
|
|
35127
|
+
if (SIGNED_IN_HEAD_TOKENS.has(currentToken) && tokens[tokenIndex + 1] === "in") {
|
|
35128
|
+
mergedTokens.push(`${currentToken}in`);
|
|
35129
|
+
tokenIndex += 1;
|
|
35130
|
+
continue;
|
|
35131
|
+
}
|
|
35132
|
+
mergedTokens.push(currentToken);
|
|
35133
|
+
}
|
|
35134
|
+
return mergedTokens;
|
|
35135
|
+
};
|
|
35136
|
+
const isAuthGuardName = (calleeName) => {
|
|
35137
|
+
const tokens = mergeSignedInTokens(tokenizeIdentifierWords(calleeName));
|
|
35138
|
+
if (tokens.length === 0) return false;
|
|
35139
|
+
let hasAssertiveVerb = false;
|
|
35140
|
+
let hasGetterVerb = false;
|
|
35141
|
+
let hasQualifier = false;
|
|
35142
|
+
let hasStrongNoun = false;
|
|
35143
|
+
let hasWeakNoun = false;
|
|
35144
|
+
for (const token of tokens) {
|
|
35145
|
+
if (AUTH_STRONG_TOKEN_PATTERN.test(token) || AUTH_STANDALONE_NOUN_TOKENS.has(token)) return true;
|
|
35146
|
+
if (AUTH_ASSERTIVE_VERB_TOKENS.has(token)) hasAssertiveVerb = true;
|
|
35147
|
+
if (AUTH_GETTER_VERB_TOKENS.has(token)) hasGetterVerb = true;
|
|
35148
|
+
if (AUTH_QUALIFIER_TOKENS.has(token)) hasQualifier = true;
|
|
35149
|
+
if (AUTH_STRONG_NOUN_TOKENS.has(token)) hasStrongNoun = true;
|
|
35150
|
+
if (AUTH_WEAK_NOUN_TOKENS.has(token)) hasWeakNoun = true;
|
|
35151
|
+
}
|
|
35152
|
+
if (hasAssertiveVerb && (hasStrongNoun || hasWeakNoun)) return true;
|
|
35153
|
+
if (hasGetterVerb && hasStrongNoun) return true;
|
|
35154
|
+
if (hasQualifier && hasWeakNoun) return true;
|
|
35155
|
+
return false;
|
|
35156
|
+
};
|
|
35157
|
+
//#endregion
|
|
34898
35158
|
//#region src/plugin/rules/server/server-auth-actions.ts
|
|
34899
35159
|
const isAsyncFunctionLikeNode = (node) => {
|
|
34900
35160
|
if (!node) return false;
|
|
@@ -34937,9 +35197,13 @@ const isMemberCallAuthRelated = (receiverNode, methodName, genericMethodNames) =
|
|
|
34937
35197
|
const getAuthCallName = (callExpression, allowedFunctionNames, genericMethodNames) => {
|
|
34938
35198
|
const calleeNode = unwrapTypeWrappedCallee(callExpression.callee);
|
|
34939
35199
|
if (!calleeNode) return null;
|
|
34940
|
-
if (isNodeOfType(calleeNode, "Identifier"))
|
|
35200
|
+
if (isNodeOfType(calleeNode, "Identifier")) {
|
|
35201
|
+
const calleeName = calleeNode.name;
|
|
35202
|
+
return allowedFunctionNames.has(calleeName) || isAuthGuardName(calleeName) ? calleeName : null;
|
|
35203
|
+
}
|
|
34941
35204
|
if (isNodeOfType(calleeNode, "MemberExpression") && isNodeOfType(calleeNode.property, "Identifier")) {
|
|
34942
35205
|
const methodName = calleeNode.property.name;
|
|
35206
|
+
if (isAuthGuardName(methodName)) return methodName;
|
|
34943
35207
|
if (!allowedFunctionNames.has(methodName)) return null;
|
|
34944
35208
|
if (!isMemberCallAuthRelated(calleeNode.object, methodName, genericMethodNames)) return null;
|
|
34945
35209
|
return methodName;
|
|
@@ -35161,6 +35425,7 @@ const serverFetchWithoutRevalidate = defineRule({
|
|
|
35161
35425
|
CallExpression(node) {
|
|
35162
35426
|
if (!isServerSideFile) return;
|
|
35163
35427
|
if (!isFetchCall(node)) return;
|
|
35428
|
+
if (isMutatingFetchCall(node)) return;
|
|
35164
35429
|
const optionsArg = node.arguments?.[1];
|
|
35165
35430
|
if (optionsArg && objectExpressionHasNextRevalidate(optionsArg)) return;
|
|
35166
35431
|
const urlArg = node.arguments?.[0];
|
|
@@ -35316,13 +35581,7 @@ const serverNoMutableModuleState = defineRule({
|
|
|
35316
35581
|
const collectDeclaredNames = (declaration) => {
|
|
35317
35582
|
const names = /* @__PURE__ */ new Set();
|
|
35318
35583
|
if (!isNodeOfType(declaration, "VariableDeclaration")) return names;
|
|
35319
|
-
for (const declarator of declaration.declarations ?? [])
|
|
35320
|
-
else if (isNodeOfType(declarator.id, "ObjectPattern")) {
|
|
35321
|
-
for (const property of declarator.id.properties ?? []) if (isNodeOfType(property, "Property") && isNodeOfType(property.value, "Identifier")) names.add(property.value.name);
|
|
35322
|
-
else if (isNodeOfType(property, "RestElement") && isNodeOfType(property.argument, "Identifier")) names.add(property.argument.name);
|
|
35323
|
-
} else if (isNodeOfType(declarator.id, "ArrayPattern")) {
|
|
35324
|
-
for (const element of declarator.id.elements ?? []) if (isNodeOfType(element, "Identifier")) names.add(element.name);
|
|
35325
|
-
}
|
|
35584
|
+
for (const declarator of declaration.declarations ?? []) collectPatternNames(declarator.id, names);
|
|
35326
35585
|
return names;
|
|
35327
35586
|
};
|
|
35328
35587
|
const declarationStartsWithAwait = (declaration) => {
|
|
@@ -35332,11 +35591,15 @@ const declarationStartsWithAwait = (declaration) => {
|
|
|
35332
35591
|
};
|
|
35333
35592
|
const declarationReadsAnyName = (declaration, names) => {
|
|
35334
35593
|
if (names.size === 0) return false;
|
|
35594
|
+
if (!isNodeOfType(declaration, "VariableDeclaration")) return false;
|
|
35335
35595
|
let didRead = false;
|
|
35336
|
-
|
|
35337
|
-
if (
|
|
35338
|
-
|
|
35339
|
-
|
|
35596
|
+
for (const declarator of declaration.declarations ?? []) {
|
|
35597
|
+
if (!declarator.init) continue;
|
|
35598
|
+
walkAst(declarator.init, (child) => {
|
|
35599
|
+
if (didRead) return;
|
|
35600
|
+
if (isNodeOfType(child, "Identifier") && names.has(child.name)) didRead = true;
|
|
35601
|
+
});
|
|
35602
|
+
}
|
|
35340
35603
|
return didRead;
|
|
35341
35604
|
};
|
|
35342
35605
|
const serverSequentialIndependentAwait = defineRule({
|
|
@@ -36596,7 +36859,7 @@ const urlPrefilledPrivilegedAction = defineRule({
|
|
|
36596
36859
|
recommendation: "Require server-side validation and explicit confirmation for URL-sourced invite, role, permission, redirect, or sharing parameters.",
|
|
36597
36860
|
scan: scanByPattern({
|
|
36598
36861
|
shouldScan: (file) => isClientSourcePath(file.relativePath),
|
|
36599
|
-
pattern: /(?<!(?:safe|valid|sanitiz|relativ|allowlist|whitelist)[\w$]*\(\s*(?:new\s+)?)\b(?:searchParams|useSearchParams\s*\(\s*\)|URLSearchParams\s*\([^)]{0,120}\))(?:[?!])?\.get(?:All)?\s*\(\s*["'](?:userstoinvite|role|permission|sharingaction|invite|admin|next|continue|returnTo|redirect_uri|callbackUrl)["']|\bsearchParams\.(?:userstoinvite|role|permission|sharingaction|invite|admin|returnTo|redirect_uri|callbackUrl)\b/i,
|
|
36862
|
+
pattern: /(?<!(?:safe|valid|sanitiz|relativ|allowlist|whitelist)[\w$]*\(\s*(?:new\s+)?(?:[\w$]+\s*\.\s*){0,4})\b(?:searchParams|useSearchParams\s*\(\s*\)|URLSearchParams\s*\([^)]{0,120}\))(?:[?!])?\.get(?:All)?\s*\(\s*["'](?:userstoinvite|role|permission|sharingaction|invite|admin|next|continue|returnTo|redirect_uri|callbackUrl)["']|\bsearchParams\.(?:userstoinvite|role|permission|sharingaction|invite|admin|returnTo|redirect_uri|callbackUrl)\b/i,
|
|
36600
36863
|
message: "Client code reads sensitive action state from the URL, which can pre-fill invites, roles, redirects, or sharing flows with attacker values."
|
|
36601
36864
|
})
|
|
36602
36865
|
});
|
|
@@ -39052,6 +39315,17 @@ const reactDoctorRules = [
|
|
|
39052
39315
|
requires: [...new Set(["react", ...noDocumentStartViewTransition.requires ?? []])]
|
|
39053
39316
|
}
|
|
39054
39317
|
},
|
|
39318
|
+
{
|
|
39319
|
+
key: "react-doctor/no-document-write",
|
|
39320
|
+
id: "no-document-write",
|
|
39321
|
+
source: "react-doctor",
|
|
39322
|
+
originallyExternal: false,
|
|
39323
|
+
rule: {
|
|
39324
|
+
...noDocumentWrite,
|
|
39325
|
+
framework: "global",
|
|
39326
|
+
category: "Performance"
|
|
39327
|
+
}
|
|
39328
|
+
},
|
|
39055
39329
|
{
|
|
39056
39330
|
key: "react-doctor/no-dynamic-import-path",
|
|
39057
39331
|
id: "no-dynamic-import-path",
|
|
@@ -39861,6 +40135,18 @@ const reactDoctorRules = [
|
|
|
39861
40135
|
requires: [...new Set(["react", ...noStaticElementInteractions.requires ?? []])]
|
|
39862
40136
|
}
|
|
39863
40137
|
},
|
|
40138
|
+
{
|
|
40139
|
+
key: "react-doctor/no-string-false-on-boolean-attribute",
|
|
40140
|
+
id: "no-string-false-on-boolean-attribute",
|
|
40141
|
+
source: "react-doctor",
|
|
40142
|
+
originallyExternal: false,
|
|
40143
|
+
rule: {
|
|
40144
|
+
...noStringFalseOnBooleanAttribute,
|
|
40145
|
+
framework: "global",
|
|
40146
|
+
category: "Bugs",
|
|
40147
|
+
requires: [...new Set(["react", ...noStringFalseOnBooleanAttribute.requires ?? []])]
|
|
40148
|
+
}
|
|
40149
|
+
},
|
|
39864
40150
|
{
|
|
39865
40151
|
key: "react-doctor/no-string-refs",
|
|
39866
40152
|
id: "no-string-refs",
|
|
@@ -39873,6 +40159,17 @@ const reactDoctorRules = [
|
|
|
39873
40159
|
requires: [...new Set(["react", ...noStringRefs.requires ?? []])]
|
|
39874
40160
|
}
|
|
39875
40161
|
},
|
|
40162
|
+
{
|
|
40163
|
+
key: "react-doctor/no-sync-xhr",
|
|
40164
|
+
id: "no-sync-xhr",
|
|
40165
|
+
source: "react-doctor",
|
|
40166
|
+
originallyExternal: false,
|
|
40167
|
+
rule: {
|
|
40168
|
+
...noSyncXhr,
|
|
40169
|
+
framework: "global",
|
|
40170
|
+
category: "Performance"
|
|
40171
|
+
}
|
|
40172
|
+
},
|
|
39876
40173
|
{
|
|
39877
40174
|
key: "react-doctor/no-this-in-sfc",
|
|
39878
40175
|
id: "no-this-in-sfc",
|
|
@@ -41981,32 +42278,6 @@ const computeUnconditionalSet = (cfg) => {
|
|
|
41981
42278
|
}
|
|
41982
42279
|
return unconditional;
|
|
41983
42280
|
};
|
|
41984
|
-
const computeDominatesExit = (cfg) => {
|
|
41985
|
-
const reachableToExit = /* @__PURE__ */ new Set();
|
|
41986
|
-
const queue = [cfg.exit];
|
|
41987
|
-
while (queue.length > 0) {
|
|
41988
|
-
const block = queue.shift();
|
|
41989
|
-
if (reachableToExit.has(block)) continue;
|
|
41990
|
-
reachableToExit.add(block);
|
|
41991
|
-
for (const edge of block.predecessors) queue.push(edge.from);
|
|
41992
|
-
}
|
|
41993
|
-
const dominatesExit = /* @__PURE__ */ new Set();
|
|
41994
|
-
const visit = (block) => {
|
|
41995
|
-
if (block === cfg.exit) return true;
|
|
41996
|
-
if (dominatesExit.has(block)) return true;
|
|
41997
|
-
if (block.successors.length === 0) return false;
|
|
41998
|
-
dominatesExit.add(block);
|
|
41999
|
-
let allReach = true;
|
|
42000
|
-
for (const edge of block.successors) if (!visit(edge.to)) {
|
|
42001
|
-
allReach = false;
|
|
42002
|
-
break;
|
|
42003
|
-
}
|
|
42004
|
-
if (!allReach) dominatesExit.delete(block);
|
|
42005
|
-
return allReach;
|
|
42006
|
-
};
|
|
42007
|
-
for (const block of cfg.blocks) visit(block);
|
|
42008
|
-
return dominatesExit;
|
|
42009
|
-
};
|
|
42010
42281
|
const analyzeControlFlow = (program) => {
|
|
42011
42282
|
nextBlockId = 0;
|
|
42012
42283
|
const functionCfgs = /* @__PURE__ */ new Map();
|
|
@@ -42014,8 +42285,7 @@ const analyzeControlFlow = (program) => {
|
|
|
42014
42285
|
const cfg = buildFunctionCfg(functionNode, body);
|
|
42015
42286
|
functionCfgs.set(functionNode, {
|
|
42016
42287
|
cfg,
|
|
42017
|
-
unconditionalSet: computeUnconditionalSet(cfg)
|
|
42018
|
-
dominatesExitSet: computeDominatesExit(cfg)
|
|
42288
|
+
unconditionalSet: computeUnconditionalSet(cfg)
|
|
42019
42289
|
});
|
|
42020
42290
|
};
|
|
42021
42291
|
if (isNodeOfType(program, "Program")) buildFor(program, {
|
|
@@ -42058,20 +42328,10 @@ const analyzeControlFlow = (program) => {
|
|
|
42058
42328
|
if (!block) return true;
|
|
42059
42329
|
return entry.unconditionalSet.has(block);
|
|
42060
42330
|
};
|
|
42061
|
-
const dominatesExit = (node) => {
|
|
42062
|
-
const owner = enclosingFunction(node);
|
|
42063
|
-
if (!owner) return true;
|
|
42064
|
-
const entry = functionCfgs.get(owner);
|
|
42065
|
-
if (!entry) return true;
|
|
42066
|
-
const block = entry.cfg.blockOf(node);
|
|
42067
|
-
if (!block) return true;
|
|
42068
|
-
return entry.dominatesExitSet.has(block);
|
|
42069
|
-
};
|
|
42070
42331
|
return {
|
|
42071
42332
|
cfgFor,
|
|
42072
42333
|
enclosingFunction,
|
|
42073
|
-
isUnconditionalFromEntry
|
|
42074
|
-
dominatesExit
|
|
42334
|
+
isUnconditionalFromEntry
|
|
42075
42335
|
};
|
|
42076
42336
|
};
|
|
42077
42337
|
//#endregion
|
|
@@ -42096,8 +42356,7 @@ const buildFallbackScopes = () => ({
|
|
|
42096
42356
|
const FALLBACK_CFG = {
|
|
42097
42357
|
cfgFor: () => null,
|
|
42098
42358
|
enclosingFunction: () => null,
|
|
42099
|
-
isUnconditionalFromEntry: () => false
|
|
42100
|
-
dominatesExit: () => false
|
|
42359
|
+
isUnconditionalFromEntry: () => false
|
|
42101
42360
|
};
|
|
42102
42361
|
const wrapWithSemanticContext = (rule) => ({
|
|
42103
42362
|
...rule,
|
|
@@ -42271,6 +42530,15 @@ const ALL_REACT_DOCTOR_RULE_KEYS = new Set(REACT_DOCTOR_RULES.map((rule) => rule
|
|
|
42271
42530
|
const FRAMEWORK_SPECIFIC_RULE_KEYS = collectFrameworkSpecificRuleKeys();
|
|
42272
42531
|
const REACT_COMPILER_RULES = toRuleMap(collectExternalRulesBySource("react-compiler"));
|
|
42273
42532
|
//#endregion
|
|
42533
|
+
//#region src/plugin/constants/cross-file-rule-ids.ts
|
|
42534
|
+
const CROSS_FILE_RULE_IDS = new Set([
|
|
42535
|
+
"no-barrel-import",
|
|
42536
|
+
"nextjs-missing-metadata",
|
|
42537
|
+
"nextjs-no-use-search-params-without-suspense",
|
|
42538
|
+
"no-mutating-reducer-state",
|
|
42539
|
+
"rn-prefer-expo-image"
|
|
42540
|
+
]);
|
|
42541
|
+
//#endregion
|
|
42274
42542
|
//#region src/plugin/rules/security-scan/utils/is-probably-text-file.ts
|
|
42275
42543
|
const isProbablyTextFile = (relativePath) => TEXT_FILE_PATTERN.test(relativePath) || DOTENV_FILE_PATTERN.test(relativePath);
|
|
42276
42544
|
//#endregion
|
|
@@ -42296,6 +42564,6 @@ const shouldReadSecurityScanContent = (relativePath, isGeneratedBundle) => isGen
|
|
|
42296
42564
|
//#region src/index.ts
|
|
42297
42565
|
var src_default = plugin;
|
|
42298
42566
|
//#endregion
|
|
42299
|
-
export { ALL_REACT_DOCTOR_RULES, ALL_REACT_DOCTOR_RULE_KEYS, EXTERNAL_RULES, FRAMEWORK_SPECIFIC_RULE_KEYS, MOTION_LIBRARY_PACKAGES, NEXTJS_RULES, PREACT_RULES, REACT_COMPILER_RULES, REACT_DOCTOR_RULES, REACT_NATIVE_DEPENDENCY_NAMES, REACT_NATIVE_DEPENDENCY_PREFIXES, REACT_NATIVE_RULES, RECOMMENDED_RULES, RULES, TANSTACK_QUERY_RULES, TANSTACK_START_RULES, classifySecurityScanFile, src_default as default, isReactNativeDependencyName, shouldReadSecurityScanContent };
|
|
42567
|
+
export { ALL_REACT_DOCTOR_RULES, ALL_REACT_DOCTOR_RULE_KEYS, CROSS_FILE_RULE_IDS, EXTERNAL_RULES, FRAMEWORK_SPECIFIC_RULE_KEYS, MOTION_LIBRARY_PACKAGES, NEXTJS_RULES, PREACT_RULES, REACT_COMPILER_RULES, REACT_DOCTOR_RULES, REACT_NATIVE_DEPENDENCY_NAMES, REACT_NATIVE_DEPENDENCY_PREFIXES, REACT_NATIVE_RULES, RECOMMENDED_RULES, RULES, TANSTACK_QUERY_RULES, TANSTACK_START_RULES, classifySecurityScanFile, src_default as default, isReactNativeDependencyName, shouldReadSecurityScanContent };
|
|
42300
42568
|
|
|
42301
42569
|
//# sourceMappingURL=index.js.map
|