oxlint-plugin-react-doctor 0.6.2-dev.59e8178 → 0.6.2-dev.ac71a3b
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +78 -151
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -378,18 +378,13 @@ const isBrowserArtifactPath = (relativePath, isGeneratedBundle) => {
|
|
|
378
378
|
const isConfigOrCiPath = (relativePath) => /(?:^|\/)(?:package\.json|Dockerfile|docker-compose\.ya?ml|\.github\/workflows\/[^/]+\.ya?ml|vercel\.json|next\.config\.[cm]?[jt]s|netlify\.toml)$/i.test(relativePath);
|
|
379
379
|
//#endregion
|
|
380
380
|
//#region src/plugin/rules/security-scan/utils/is-production-file-path.ts
|
|
381
|
-
const classificationCacheByPattern = /* @__PURE__ */ new Map();
|
|
382
381
|
const isProductionFilePath = (relativePath, sourceFilePattern) => {
|
|
383
|
-
|
|
384
|
-
if (
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
if (cached !== void 0) return cached;
|
|
390
|
-
const isProduction = sourceFilePattern.test(relativePath) && !TEST_CONTEXT_PATTERN.test(relativePath) && !BUILD_SCRIPT_CONTEXT_PATTERN.test(relativePath) && !DOCUMENTATION_CONTEXT_PATTERN.test(relativePath) && !GENERATED_SOURCE_CONTEXT_PATTERN.test(relativePath);
|
|
391
|
-
classificationByPath.set(relativePath, isProduction);
|
|
392
|
-
return isProduction;
|
|
382
|
+
if (!sourceFilePattern.test(relativePath)) return false;
|
|
383
|
+
if (TEST_CONTEXT_PATTERN.test(relativePath)) return false;
|
|
384
|
+
if (BUILD_SCRIPT_CONTEXT_PATTERN.test(relativePath)) return false;
|
|
385
|
+
if (DOCUMENTATION_CONTEXT_PATTERN.test(relativePath)) return false;
|
|
386
|
+
if (GENERATED_SOURCE_CONTEXT_PATTERN.test(relativePath)) return false;
|
|
387
|
+
return true;
|
|
393
388
|
};
|
|
394
389
|
//#endregion
|
|
395
390
|
//#region src/plugin/rules/security-scan/utils/is-production-source-path.ts
|
|
@@ -1682,7 +1677,7 @@ const isNextjsMetadataImageRouteFilename = (rawFilename) => {
|
|
|
1682
1677
|
};
|
|
1683
1678
|
//#endregion
|
|
1684
1679
|
//#region src/plugin/utils/normalize-filename.ts
|
|
1685
|
-
const normalizeFilename = (filename) => filename.
|
|
1680
|
+
const normalizeFilename = (filename) => filename.replaceAll("\\", "/");
|
|
1686
1681
|
//#endregion
|
|
1687
1682
|
//#region src/plugin/utils/is-generated-image-render-context.ts
|
|
1688
1683
|
const IMAGE_RESPONSE_MODULES = ["next/og", "@vercel/og"];
|
|
@@ -4843,8 +4838,7 @@ const FORM_CONTROL_TAGS = new Set([
|
|
|
4843
4838
|
]);
|
|
4844
4839
|
const resolveSettings$48 = (settings) => {
|
|
4845
4840
|
const reactDoctor = settings?.["react-doctor"];
|
|
4846
|
-
|
|
4847
|
-
return { inputComponents: new Set(ruleSettings.inputComponents ?? []) };
|
|
4841
|
+
return { inputComponents: (typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.autocompleteValid ?? {} : {}).inputComponents ?? [] };
|
|
4848
4842
|
};
|
|
4849
4843
|
const autocompleteValid = defineRule({
|
|
4850
4844
|
id: "autocomplete-valid",
|
|
@@ -4857,7 +4851,7 @@ const autocompleteValid = defineRule({
|
|
|
4857
4851
|
const settings = resolveSettings$48(context.settings);
|
|
4858
4852
|
return { JSXOpeningElement: (node) => {
|
|
4859
4853
|
const tag = getElementType(node, context.settings);
|
|
4860
|
-
if (!FORM_CONTROL_TAGS.has(tag) && !settings.inputComponents.
|
|
4854
|
+
if (!FORM_CONTROL_TAGS.has(tag) && !settings.inputComponents.includes(tag)) return;
|
|
4861
4855
|
const attribute = hasJsxPropIgnoreCase(node.attributes, "autoComplete");
|
|
4862
4856
|
if (!attribute) return;
|
|
4863
4857
|
const value = getJsxPropStringValue(attribute);
|
|
@@ -8637,7 +8631,7 @@ const DEFAULT_HEADING_TAGS = [
|
|
|
8637
8631
|
const resolveSettings$40 = (settings) => {
|
|
8638
8632
|
const reactDoctor = settings?.["react-doctor"];
|
|
8639
8633
|
const ruleSettings = typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.headingHasContent ?? {} : {};
|
|
8640
|
-
return { headingTags:
|
|
8634
|
+
return { headingTags: [...DEFAULT_HEADING_TAGS, ...ruleSettings.components ?? []] };
|
|
8641
8635
|
};
|
|
8642
8636
|
const headingHasContent = defineRule({
|
|
8643
8637
|
id: "heading-has-content",
|
|
@@ -8650,7 +8644,7 @@ const headingHasContent = defineRule({
|
|
|
8650
8644
|
const settings = resolveSettings$40(context.settings);
|
|
8651
8645
|
return { JSXOpeningElement(node) {
|
|
8652
8646
|
const elementType = getElementType(node, context.settings);
|
|
8653
|
-
if (!settings.headingTags.
|
|
8647
|
+
if (!settings.headingTags.includes(elementType)) return;
|
|
8654
8648
|
const parent = node.parent;
|
|
8655
8649
|
if (parent && isNodeOfType(parent, "JSXElement")) {
|
|
8656
8650
|
if (objectHasAccessibleChild(parent, context.settings)) return;
|
|
@@ -9556,7 +9550,6 @@ const KEYBOARD_EVENT_HANDLERS = [
|
|
|
9556
9550
|
"onKeyUp"
|
|
9557
9551
|
];
|
|
9558
9552
|
const ALL_EVENT_HANDLERS = [...MOUSE_EVENT_HANDLERS, ...KEYBOARD_EVENT_HANDLERS];
|
|
9559
|
-
const ALL_EVENT_HANDLERS_LOWER = new Set(ALL_EVENT_HANDLERS.map((handlerName) => handlerName.toLowerCase()));
|
|
9560
9553
|
//#endregion
|
|
9561
9554
|
//#region src/plugin/utils/is-disabled-element.ts
|
|
9562
9555
|
const isDisabledElement = (openingElement) => {
|
|
@@ -9616,16 +9609,7 @@ const interactiveSupportsFocus = defineRule({
|
|
|
9616
9609
|
const roleAttribute = hasJsxPropIgnoreCase(node.attributes, "role");
|
|
9617
9610
|
const role = roleAttribute ? getJsxPropStringValue(roleAttribute) : null;
|
|
9618
9611
|
if (!role) return;
|
|
9619
|
-
|
|
9620
|
-
for (const attribute of node.attributes) {
|
|
9621
|
-
if (!isNodeOfType(attribute, "JSXAttribute")) continue;
|
|
9622
|
-
const attributeName = getJsxAttributeName(attribute.name);
|
|
9623
|
-
if (attributeName && ALL_EVENT_HANDLERS_LOWER.has(attributeName.toLowerCase())) {
|
|
9624
|
-
hasInteractiveHandler = true;
|
|
9625
|
-
break;
|
|
9626
|
-
}
|
|
9627
|
-
}
|
|
9628
|
-
if (!hasInteractiveHandler) return;
|
|
9612
|
+
if (!ALL_EVENT_HANDLERS.some((handler) => Boolean(hasJsxPropIgnoreCase(node.attributes, handler)))) return;
|
|
9629
9613
|
const elementType = getElementType(node, context.settings);
|
|
9630
9614
|
if (!HTML_TAGS.has(elementType)) return;
|
|
9631
9615
|
if (isDisabledElement(node) || isHiddenFromScreenReader(node, context.settings) || isPresentationRole(node)) return;
|
|
@@ -14474,14 +14458,14 @@ const keyLifecycleRisk = defineRule({
|
|
|
14474
14458
|
//#region src/plugin/rules/a11y/label-has-associated-control.ts
|
|
14475
14459
|
const MESSAGE_NO_LABEL = "Blind users can't identify this field because screen readers find no label text, so add visible text, `aria-label`, or `aria-labelledby`.";
|
|
14476
14460
|
const MESSAGE_NO_CONTROL = "Screen reader users can't tell which input this label names because it's tied to none, so add `htmlFor` or wrap the input inside it.";
|
|
14477
|
-
const DEFAULT_CONTROL_COMPONENTS =
|
|
14461
|
+
const DEFAULT_CONTROL_COMPONENTS = [
|
|
14478
14462
|
"input",
|
|
14479
14463
|
"meter",
|
|
14480
14464
|
"output",
|
|
14481
14465
|
"progress",
|
|
14482
14466
|
"select",
|
|
14483
14467
|
"textarea"
|
|
14484
|
-
]
|
|
14468
|
+
];
|
|
14485
14469
|
const DEFAULT_LABEL_ATTRIBUTES = [
|
|
14486
14470
|
"alt",
|
|
14487
14471
|
"aria-label",
|
|
@@ -14493,8 +14477,8 @@ const resolveSettings$24 = (settings) => {
|
|
|
14493
14477
|
const jsxA11y = settings?.["jsx-a11y"];
|
|
14494
14478
|
const forAttributes = (typeof jsxA11y === "object" && jsxA11y !== null ? jsxA11y : {}).attributes?.for ?? ["htmlFor"];
|
|
14495
14479
|
return {
|
|
14496
|
-
labelComponents:
|
|
14497
|
-
labelAttributes: new Set([...DEFAULT_LABEL_ATTRIBUTES, ...ruleSettings.labelAttributes ?? []]),
|
|
14480
|
+
labelComponents: ["label", ...ruleSettings.labelComponents ?? []].sort(),
|
|
14481
|
+
labelAttributes: [...new Set([...DEFAULT_LABEL_ATTRIBUTES, ...ruleSettings.labelAttributes ?? []])].sort(),
|
|
14498
14482
|
controlComponents: ruleSettings.controlComponents ?? [],
|
|
14499
14483
|
assert: ruleSettings.assert ?? "either",
|
|
14500
14484
|
depth: Math.min(ruleSettings.depth ?? 5, 25),
|
|
@@ -14502,7 +14486,7 @@ const resolveSettings$24 = (settings) => {
|
|
|
14502
14486
|
};
|
|
14503
14487
|
};
|
|
14504
14488
|
const isControlComponent = (tagName, controlComponents) => {
|
|
14505
|
-
if (DEFAULT_CONTROL_COMPONENTS.
|
|
14489
|
+
if (DEFAULT_CONTROL_COMPONENTS.includes(tagName)) return true;
|
|
14506
14490
|
return controlComponents.some((pattern) => compileGlob(pattern).test(tagName));
|
|
14507
14491
|
};
|
|
14508
14492
|
const searchForNestedControl = (child, currentDepth, searchContext) => {
|
|
@@ -14528,7 +14512,7 @@ const searchForAccessibleLabel = (child, currentDepth, searchContext) => {
|
|
|
14528
14512
|
const attributeName = attribute.name;
|
|
14529
14513
|
if (!isNodeOfType(attributeName, "JSXIdentifier")) continue;
|
|
14530
14514
|
const propName = getJsxAttributeName(attributeName);
|
|
14531
|
-
if (!propName || !searchContext.labelAttributes.
|
|
14515
|
+
if (!propName || !searchContext.labelAttributes.includes(propName)) continue;
|
|
14532
14516
|
const attributeValue = attribute.value;
|
|
14533
14517
|
if (!attributeValue) continue;
|
|
14534
14518
|
if (isNodeOfType(attributeValue, "Literal") && typeof attributeValue.value === "string") {
|
|
@@ -14550,7 +14534,7 @@ const hasAccessibleLabel = (element, searchContext) => {
|
|
|
14550
14534
|
const attributeName = attribute.name;
|
|
14551
14535
|
if (!isNodeOfType(attributeName, "JSXIdentifier")) continue;
|
|
14552
14536
|
const propName = getJsxAttributeName(attributeName);
|
|
14553
|
-
if (propName && searchContext.labelAttributes.
|
|
14537
|
+
if (propName && searchContext.labelAttributes.includes(propName)) return true;
|
|
14554
14538
|
}
|
|
14555
14539
|
for (const child of element.children) if (searchForAccessibleLabel(child, 1, searchContext)) return true;
|
|
14556
14540
|
return false;
|
|
@@ -14573,7 +14557,7 @@ const labelHasAssociatedControl = defineRule({
|
|
|
14573
14557
|
if (isTestlikeFile) return;
|
|
14574
14558
|
const opening = node.openingElement;
|
|
14575
14559
|
const tagName = getElementType(opening, context.settings);
|
|
14576
|
-
if (!settings.labelComponents.
|
|
14560
|
+
if (!settings.labelComponents.includes(tagName)) return;
|
|
14577
14561
|
const hasHtmlFor = settings.forAttributes.some((attributeName) => Boolean(hasJsxPropIgnoreCase(opening.attributes, attributeName)));
|
|
14578
14562
|
const searchContext = {
|
|
14579
14563
|
depth: settings.depth,
|
|
@@ -15162,9 +15146,9 @@ const resolveSettings$23 = (settings) => {
|
|
|
15162
15146
|
const reactDoctor = settings?.["react-doctor"];
|
|
15163
15147
|
const ruleSettings = typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.mediaHasCaption ?? {} : {};
|
|
15164
15148
|
return {
|
|
15165
|
-
audio:
|
|
15166
|
-
video:
|
|
15167
|
-
track:
|
|
15149
|
+
audio: [...DEFAULT_AUDIO, ...ruleSettings.audio ?? []],
|
|
15150
|
+
video: [...DEFAULT_VIDEO, ...ruleSettings.video ?? []],
|
|
15151
|
+
track: [...DEFAULT_TRACK, ...ruleSettings.track ?? []]
|
|
15168
15152
|
};
|
|
15169
15153
|
};
|
|
15170
15154
|
const evaluateMuted = (attribute) => {
|
|
@@ -15193,7 +15177,7 @@ const childMayRenderTrack = (child, trackTags, settings) => {
|
|
|
15193
15177
|
let rendersCaptionTrack = false;
|
|
15194
15178
|
walkAst(expression, (inner) => {
|
|
15195
15179
|
if (rendersCaptionTrack) return false;
|
|
15196
|
-
if (isNodeOfType(inner, "JSXElement") && trackTags.
|
|
15180
|
+
if (isNodeOfType(inner, "JSXElement") && trackTags.includes(getElementType(inner.openingElement, settings)) && trackKindMightBeCaptions(inner.openingElement)) {
|
|
15197
15181
|
rendersCaptionTrack = true;
|
|
15198
15182
|
return false;
|
|
15199
15183
|
}
|
|
@@ -15211,7 +15195,7 @@ const mediaHasCaption = defineRule({
|
|
|
15211
15195
|
const settings = resolveSettings$23(context.settings);
|
|
15212
15196
|
return { JSXOpeningElement(node) {
|
|
15213
15197
|
const tag = getElementType(node, context.settings);
|
|
15214
|
-
if (!(settings.audio.
|
|
15198
|
+
if (!(settings.audio.includes(tag) || settings.video.includes(tag))) return;
|
|
15215
15199
|
if (evaluateMuted(hasJsxPropIgnoreCase(node.attributes, "muted")) === true) return;
|
|
15216
15200
|
const parent = node.parent;
|
|
15217
15201
|
if (!parent || !isNodeOfType(parent, "JSXElement")) {
|
|
@@ -15226,7 +15210,7 @@ const mediaHasCaption = defineRule({
|
|
|
15226
15210
|
if (!isNodeOfType(child, "JSXElement")) return false;
|
|
15227
15211
|
const opening = child.openingElement;
|
|
15228
15212
|
const childTag = getElementType(opening, context.settings);
|
|
15229
|
-
if (!settings.track.
|
|
15213
|
+
if (!settings.track.includes(childTag)) return false;
|
|
15230
15214
|
const kindAttribute = hasJsxPropIgnoreCase(opening.attributes, "kind");
|
|
15231
15215
|
if (!kindAttribute) return false;
|
|
15232
15216
|
let kindValue = kindAttribute.value;
|
|
@@ -15537,30 +15521,16 @@ const stripJsComments = (sourceText) => sourceText.replace(BLOCK_COMMENT_PATTERN
|
|
|
15537
15521
|
const DEFAULT_EXPORT_DECLARATION_PATTERN = /^\s*export\s+default\b/m;
|
|
15538
15522
|
const NAMED_EXPORT_DECLARATION_PATTERN = /^\s*export\s+(?:declare\s+)?(?:(?:async\s+)?function|(?:abstract\s+)?class|const|let|var|enum|interface|type)\s+([\w$]+)/gm;
|
|
15539
15523
|
const LOCAL_EXPORT_SPECIFIER_DECLARATION_PATTERN$1 = /^\s*export\s+(?:type\s+)?\{([\s\S]*?)\}(?:\s+from\s+["'][^"']+["'])?\s*;?\s*(?:(?:\/\/[^\n]*)?\s*)/gm;
|
|
15540
|
-
const
|
|
15524
|
+
const doesSourceTextExportName = (sourceText, exportedName) => {
|
|
15541
15525
|
const strippedSource = stripJsComments(sourceText);
|
|
15542
|
-
|
|
15543
|
-
|
|
15544
|
-
for (const match of strippedSource.matchAll(
|
|
15545
|
-
|
|
15546
|
-
const specifiersText = match[1] ?? "";
|
|
15547
|
-
for (const specifier of parseExportSpecifiers(specifiersText, false)) exportedNames.add(specifier.exportedName);
|
|
15548
|
-
}
|
|
15549
|
-
return exportedNames;
|
|
15526
|
+
if (exportedName === "default" && DEFAULT_EXPORT_DECLARATION_PATTERN.test(strippedSource)) return true;
|
|
15527
|
+
for (const match of strippedSource.matchAll(NAMED_EXPORT_DECLARATION_PATTERN)) if (match[1] === exportedName) return true;
|
|
15528
|
+
for (const match of strippedSource.matchAll(LOCAL_EXPORT_SPECIFIER_DECLARATION_PATTERN$1)) if (parseExportSpecifiers(match[1] ?? "", false).map((specifier) => specifier.exportedName).includes(exportedName)) return true;
|
|
15529
|
+
return false;
|
|
15550
15530
|
};
|
|
15551
|
-
const exportNamesCache = /* @__PURE__ */ new Map();
|
|
15552
15531
|
const doesModuleExportName = (filePath, exportedName) => {
|
|
15553
15532
|
try {
|
|
15554
|
-
|
|
15555
|
-
const cached = exportNamesCache.get(filePath);
|
|
15556
|
-
if (cached && cached.mtimeMs === fileStat.mtimeMs && cached.size === fileStat.size) return cached.exportedNames.has(exportedName);
|
|
15557
|
-
const exportedNames = collectSourceTextExportNames(fs.readFileSync(filePath, "utf8"));
|
|
15558
|
-
exportNamesCache.set(filePath, {
|
|
15559
|
-
mtimeMs: fileStat.mtimeMs,
|
|
15560
|
-
size: fileStat.size,
|
|
15561
|
-
exportedNames
|
|
15562
|
-
});
|
|
15563
|
-
return exportedNames.has(exportedName);
|
|
15533
|
+
return doesSourceTextExportName(fs.readFileSync(filePath, "utf8"), exportedName);
|
|
15564
15534
|
} catch {
|
|
15565
15535
|
return false;
|
|
15566
15536
|
}
|
|
@@ -17443,15 +17413,8 @@ const getProgramAnalysis = (anyNode) => {
|
|
|
17443
17413
|
programToAnalysis.set(programNode, analysis);
|
|
17444
17414
|
return analysis;
|
|
17445
17415
|
};
|
|
17446
|
-
const scopeByNodeCache = /* @__PURE__ */ new WeakMap();
|
|
17447
17416
|
const getScopeForNode = (node, manager) => {
|
|
17448
17417
|
if (!node.range) return null;
|
|
17449
|
-
let scopeByNode = scopeByNodeCache.get(manager);
|
|
17450
|
-
if (!scopeByNode) {
|
|
17451
|
-
scopeByNode = /* @__PURE__ */ new WeakMap();
|
|
17452
|
-
scopeByNodeCache.set(manager, scopeByNode);
|
|
17453
|
-
}
|
|
17454
|
-
if (scopeByNode.has(node)) return scopeByNode.get(node) ?? null;
|
|
17455
17418
|
let bestScope = null;
|
|
17456
17419
|
let bestSize = Infinity;
|
|
17457
17420
|
for (const scope of manager.scopes) {
|
|
@@ -17464,7 +17427,6 @@ const getScopeForNode = (node, manager) => {
|
|
|
17464
17427
|
bestScope = scope;
|
|
17465
17428
|
}
|
|
17466
17429
|
}
|
|
17467
|
-
scopeByNode.set(node, bestScope);
|
|
17468
17430
|
return bestScope;
|
|
17469
17431
|
};
|
|
17470
17432
|
//#endregion
|
|
@@ -17499,20 +17461,11 @@ const descend = (node, visit, visited = /* @__PURE__ */ new Set()) => {
|
|
|
17499
17461
|
} else if (isAstNode(child)) descend(child, visit, visited);
|
|
17500
17462
|
}
|
|
17501
17463
|
};
|
|
17502
|
-
const upstreamRefsCache = /* @__PURE__ */ new WeakMap();
|
|
17503
17464
|
const getUpstreamRefs = (analysis, ref) => {
|
|
17504
|
-
let upstreamByRef = upstreamRefsCache.get(analysis);
|
|
17505
|
-
if (!upstreamByRef) {
|
|
17506
|
-
upstreamByRef = /* @__PURE__ */ new WeakMap();
|
|
17507
|
-
upstreamRefsCache.set(analysis, upstreamByRef);
|
|
17508
|
-
}
|
|
17509
|
-
const cached = upstreamByRef.get(ref);
|
|
17510
|
-
if (cached) return cached;
|
|
17511
17465
|
const refs = [];
|
|
17512
17466
|
ascend(analysis, ref, (upRef) => {
|
|
17513
17467
|
refs.push(upRef);
|
|
17514
17468
|
});
|
|
17515
|
-
upstreamByRef.set(ref, refs);
|
|
17516
17469
|
return refs;
|
|
17517
17470
|
};
|
|
17518
17471
|
const findDownstreamNodes = (topNode, type) => {
|
|
@@ -17522,24 +17475,11 @@ const findDownstreamNodes = (topNode, type) => {
|
|
|
17522
17475
|
});
|
|
17523
17476
|
return nodes;
|
|
17524
17477
|
};
|
|
17525
|
-
const refByIdentifierCache = /* @__PURE__ */ new WeakMap();
|
|
17526
17478
|
const getRef = (analysis, identifier) => {
|
|
17527
|
-
let refByIdentifier = refByIdentifierCache.get(analysis);
|
|
17528
|
-
if (!refByIdentifier) {
|
|
17529
|
-
refByIdentifier = /* @__PURE__ */ new WeakMap();
|
|
17530
|
-
refByIdentifierCache.set(analysis, refByIdentifier);
|
|
17531
|
-
}
|
|
17532
|
-
if (refByIdentifier.has(identifier)) return refByIdentifier.get(identifier) ?? null;
|
|
17533
|
-
let resolvedReference = null;
|
|
17534
17479
|
const scope = getScopeForNode(identifier, analysis.scopeManager);
|
|
17535
|
-
if (scope)
|
|
17536
|
-
|
|
17537
|
-
|
|
17538
|
-
break;
|
|
17539
|
-
}
|
|
17540
|
-
}
|
|
17541
|
-
refByIdentifier.set(identifier, resolvedReference);
|
|
17542
|
-
return resolvedReference;
|
|
17480
|
+
if (!scope) return null;
|
|
17481
|
+
for (const reference of scope.references) if (reference.identifier === identifier) return reference;
|
|
17482
|
+
return null;
|
|
17543
17483
|
};
|
|
17544
17484
|
const downstreamRefsCache = /* @__PURE__ */ new WeakMap();
|
|
17545
17485
|
const getDownstreamRefs = (analysis, node) => {
|
|
@@ -17614,6 +17554,22 @@ const isEventualCallTo = (analysis, ref, predicate) => {
|
|
|
17614
17554
|
};
|
|
17615
17555
|
//#endregion
|
|
17616
17556
|
//#region src/plugin/rules/state-and-effects/utils/effect/react.ts
|
|
17557
|
+
const getOuterScopeContaining = (analysis, node) => {
|
|
17558
|
+
if (!node.range) return null;
|
|
17559
|
+
let best = null;
|
|
17560
|
+
let bestSize = Infinity;
|
|
17561
|
+
for (const scope of analysis.scopeManager.scopes) {
|
|
17562
|
+
const block = scope.block;
|
|
17563
|
+
if (!block?.range) continue;
|
|
17564
|
+
if (node.range[0] < block.range[0] || node.range[1] > block.range[1]) continue;
|
|
17565
|
+
const size = block.range[1] - block.range[0];
|
|
17566
|
+
if (size <= bestSize) {
|
|
17567
|
+
bestSize = size;
|
|
17568
|
+
best = scope;
|
|
17569
|
+
}
|
|
17570
|
+
}
|
|
17571
|
+
return best;
|
|
17572
|
+
};
|
|
17617
17573
|
const KNOWN_PURE_HOC_NAMES = new Set(["memo", "forwardRef"]);
|
|
17618
17574
|
const startsWithUppercase = (name) => Boolean(name && name.length > 0 && name[0] >= "A" && name[0] <= "Z");
|
|
17619
17575
|
const isReactFunctionalComponent = (node) => {
|
|
@@ -17644,7 +17600,7 @@ const isReactFunctionalHOC = (analysis, node) => {
|
|
|
17644
17600
|
const isWrappedSeparately = () => {
|
|
17645
17601
|
if (!isNodeOfType(node.id, "Identifier")) return false;
|
|
17646
17602
|
const bindingName = node.id.name;
|
|
17647
|
-
const containingScope =
|
|
17603
|
+
const containingScope = getOuterScopeContaining(analysis, node);
|
|
17648
17604
|
if (!containingScope) return false;
|
|
17649
17605
|
const variable = containingScope.variables.find((v) => v.name === bindingName);
|
|
17650
17606
|
if (!variable) return false;
|
|
@@ -24641,14 +24597,14 @@ const collectRoleBranches = (expression, out) => {
|
|
|
24641
24597
|
out.hasOpaqueBranch = true;
|
|
24642
24598
|
};
|
|
24643
24599
|
const buildMessage$12 = (tag) => `Keyboard & screen reader users can't trigger this \`<${tag}>\` because it isn't interactive, so use a button or link or add an interactive role.`;
|
|
24644
|
-
const
|
|
24600
|
+
const INTERACTIVE_HANDLERS = [
|
|
24645
24601
|
"onClick",
|
|
24646
24602
|
"onMouseDown",
|
|
24647
24603
|
"onMouseUp",
|
|
24648
24604
|
"onKeyDown",
|
|
24649
24605
|
"onKeyPress",
|
|
24650
24606
|
"onKeyUp"
|
|
24651
|
-
]
|
|
24607
|
+
];
|
|
24652
24608
|
const noNoninteractiveElementInteractions = defineRule({
|
|
24653
24609
|
id: "no-noninteractive-element-interactions",
|
|
24654
24610
|
title: "Handler on non-interactive element",
|
|
@@ -24663,16 +24619,7 @@ const noNoninteractiveElementInteractions = defineRule({
|
|
|
24663
24619
|
const tag = getElementType(node, context.settings);
|
|
24664
24620
|
if (!NON_INTERACTIVE_ELEMENTS.has(tag)) return;
|
|
24665
24621
|
if (tag === "label") return;
|
|
24666
|
-
|
|
24667
|
-
for (const attribute of node.attributes) {
|
|
24668
|
-
if (!isNodeOfType(attribute, "JSXAttribute")) continue;
|
|
24669
|
-
const attributeName = getJsxAttributeName(attribute.name);
|
|
24670
|
-
if (attributeName && INTERACTIVE_HANDLERS_LOWER.has(attributeName.toLowerCase())) {
|
|
24671
|
-
hasHandler = true;
|
|
24672
|
-
break;
|
|
24673
|
-
}
|
|
24674
|
-
}
|
|
24675
|
-
if (!hasHandler) return;
|
|
24622
|
+
if (!INTERACTIVE_HANDLERS.some((handler) => hasJsxPropIgnoreCase(node.attributes, handler))) return;
|
|
24676
24623
|
if (isHiddenFromScreenReader(node, context.settings)) return;
|
|
24677
24624
|
const roleAttr = hasJsxPropIgnoreCase(node.attributes, "role");
|
|
24678
24625
|
if (roleAttr) {
|
|
@@ -25985,22 +25932,16 @@ const ELEMENT_ROLE_PAIRS = [
|
|
|
25985
25932
|
["tr", "row"],
|
|
25986
25933
|
["ul", "list"]
|
|
25987
25934
|
];
|
|
25988
|
-
const
|
|
25989
|
-
const
|
|
25990
|
-
const
|
|
25991
|
-
|
|
25992
|
-
|
|
25993
|
-
|
|
25994
|
-
|
|
25995
|
-
|
|
25996
|
-
|
|
25997
|
-
}
|
|
25998
|
-
return lookup;
|
|
25935
|
+
const getElementImplicitRoles = (tag) => {
|
|
25936
|
+
const out = [];
|
|
25937
|
+
for (const [element, role] of ELEMENT_ROLE_PAIRS) if (element === tag && !out.includes(role)) out.push(role);
|
|
25938
|
+
return out;
|
|
25939
|
+
};
|
|
25940
|
+
const getTagsForRole = (role) => {
|
|
25941
|
+
const out = [];
|
|
25942
|
+
for (const [element, r] of ELEMENT_ROLE_PAIRS) if (r === role && !out.includes(element)) out.push(element);
|
|
25943
|
+
return out;
|
|
25999
25944
|
};
|
|
26000
|
-
const IMPLICIT_ROLES_BY_TAG = buildLookup(ELEMENT_ROLE_PAIRS, 0);
|
|
26001
|
-
const TAGS_BY_ROLE = buildLookup(ELEMENT_ROLE_PAIRS, 1);
|
|
26002
|
-
const getElementImplicitRoles = (tag) => IMPLICIT_ROLES_BY_TAG.get(tag) ?? EMPTY_ROLE_LIST;
|
|
26003
|
-
const getTagsForRole = (role) => TAGS_BY_ROLE.get(role) ?? EMPTY_ROLE_LIST;
|
|
26004
25945
|
//#endregion
|
|
26005
25946
|
//#region src/plugin/utils/get-implicit-role.ts
|
|
26006
25947
|
const getImplicitRole = (node, elementType) => {
|
|
@@ -26500,7 +26441,6 @@ const TANSTACK_ROUTE_PROPERTY_ORDER = [
|
|
|
26500
26441
|
"headers",
|
|
26501
26442
|
"remountDeps"
|
|
26502
26443
|
];
|
|
26503
|
-
const TANSTACK_ROUTE_PROPERTY_INDEX = new Map(TANSTACK_ROUTE_PROPERTY_ORDER.map((propertyName, orderIndex) => [propertyName, orderIndex]));
|
|
26504
26444
|
const TANSTACK_ROUTE_CREATION_FUNCTIONS = new Set([
|
|
26505
26445
|
"createFileRoute",
|
|
26506
26446
|
"createRoute",
|
|
@@ -26516,7 +26456,6 @@ const TANSTACK_MIDDLEWARE_METHOD_ORDER = [
|
|
|
26516
26456
|
"server",
|
|
26517
26457
|
"handler"
|
|
26518
26458
|
];
|
|
26519
|
-
const TANSTACK_MIDDLEWARE_METHOD_INDEX = new Map(TANSTACK_MIDDLEWARE_METHOD_ORDER.map((methodName, orderIndex) => [methodName, orderIndex]));
|
|
26520
26459
|
const TANSTACK_REDIRECT_FUNCTIONS = new Set(["redirect", "notFound"]);
|
|
26521
26460
|
const TANSTACK_SERVER_FN_FILE_PATTERN = /\.functions(\.[jt]sx?)?$/;
|
|
26522
26461
|
const TANSTACK_QUERY_HOOKS = new Set([
|
|
@@ -27221,21 +27160,14 @@ const noStaticElementInteractions = defineRule({
|
|
|
27221
27160
|
category: "Accessibility",
|
|
27222
27161
|
create: (context) => {
|
|
27223
27162
|
const settings = resolveSettings$12(context.settings);
|
|
27224
|
-
const handlersLower = new Set(settings.handlers.map((handlerName) => handlerName.toLowerCase()));
|
|
27225
27163
|
const isTestlikeFile = isTestlikeFilename(context.filename);
|
|
27226
27164
|
return { JSXOpeningElement(node) {
|
|
27227
27165
|
if (isTestlikeFile) return;
|
|
27228
27166
|
let hasNonBlockerHandler = false;
|
|
27229
27167
|
let hasAnyHandler = false;
|
|
27230
|
-
|
|
27231
|
-
|
|
27232
|
-
if (!
|
|
27233
|
-
const attributeName = getJsxAttributeName(attribute.name);
|
|
27234
|
-
if (!attributeName) continue;
|
|
27235
|
-
const handlerNameLower = attributeName.toLowerCase();
|
|
27236
|
-
if (!handlersLower.has(handlerNameLower)) continue;
|
|
27237
|
-
if (seenHandlerNames?.has(handlerNameLower)) continue;
|
|
27238
|
-
(seenHandlerNames ??= /* @__PURE__ */ new Set()).add(handlerNameLower);
|
|
27168
|
+
for (const handler of settings.handlers) {
|
|
27169
|
+
const attribute = hasJsxPropIgnoreCase(node.attributes, handler);
|
|
27170
|
+
if (!attribute) continue;
|
|
27239
27171
|
if (isNullValue(attribute)) continue;
|
|
27240
27172
|
hasAnyHandler = true;
|
|
27241
27173
|
if (!isPureEventBlockerHandler(attribute)) {
|
|
@@ -28416,13 +28348,13 @@ const DOM_ATTRIBUTES_TO_CAMEL = new Map([
|
|
|
28416
28348
|
["xml:lang", "xmlLang"],
|
|
28417
28349
|
["xml:space", "xmlSpace"]
|
|
28418
28350
|
]);
|
|
28419
|
-
const
|
|
28351
|
+
const DOM_PROPERTIES_IGNORE_CASE = [
|
|
28420
28352
|
"charset",
|
|
28421
28353
|
"allowFullScreen",
|
|
28422
28354
|
"webkitAllowFullScreen",
|
|
28423
28355
|
"mozAllowFullScreen",
|
|
28424
28356
|
"webkitDirectory"
|
|
28425
|
-
]
|
|
28357
|
+
];
|
|
28426
28358
|
//#endregion
|
|
28427
28359
|
//#region src/plugin/constants/dom-property-tags.ts
|
|
28428
28360
|
const DOM_PROPERTY_TO_ALLOWED_TAGS = new Map([
|
|
@@ -28626,7 +28558,10 @@ const matchesHtmlTagConventions = (tagName) => {
|
|
|
28626
28558
|
if (!(firstCharacter >= 97 && firstCharacter <= 122)) return false;
|
|
28627
28559
|
return !tagName.includes("-");
|
|
28628
28560
|
};
|
|
28629
|
-
const normalizeAttributeCase = (name) =>
|
|
28561
|
+
const normalizeAttributeCase = (name) => {
|
|
28562
|
+
for (const ignoreCaseName of DOM_PROPERTIES_IGNORE_CASE) if (ignoreCaseName.toLowerCase() === name.toLowerCase()) return ignoreCaseName;
|
|
28563
|
+
return name;
|
|
28564
|
+
};
|
|
28630
28565
|
const hasUppercaseChar = (input) => /[A-Z]/.test(input);
|
|
28631
28566
|
const INVALID_PROP_ON_TAG = (propName, allowedTags) => `React ignores \`${propName}\` here because it only works on these tags: ${allowedTags}.`;
|
|
28632
28567
|
const DATA_LOWERCASE_REQUIRED = () => `React drops this \`data-*\` prop because of its capital letters.`;
|
|
@@ -34823,10 +34758,9 @@ const resolveTextBoundaryName = (openingElement) => {
|
|
|
34823
34758
|
if (isNodeOfType(openingElement.name, "JSXNamespacedName")) return openingElement.name.namespace.name;
|
|
34824
34759
|
return resolveJsxElementName(openingElement);
|
|
34825
34760
|
};
|
|
34826
|
-
const TEXT_COMPONENT_KEYWORDS = [...REACT_NATIVE_TEXT_COMPONENT_KEYWORDS];
|
|
34827
34761
|
const isTextHandlingComponent = (elementName) => {
|
|
34828
34762
|
if (REACT_NATIVE_TEXT_COMPONENTS.has(elementName)) return true;
|
|
34829
|
-
return
|
|
34763
|
+
return [...REACT_NATIVE_TEXT_COMPONENT_KEYWORDS].some((keyword) => elementName.includes(keyword));
|
|
34830
34764
|
};
|
|
34831
34765
|
const isTransparentTextWrapper = (elementName) => elementName !== null && REACT_NATIVE_TEXT_TRANSPARENT_COMPONENTS.has(elementName);
|
|
34832
34766
|
const isInsideTextHandlingComponent = (node) => {
|
|
@@ -41330,10 +41264,10 @@ const tanstackStartRoutePropertyOrder = defineRule({
|
|
|
41330
41264
|
const propertyName = getPropertyKeyName(property);
|
|
41331
41265
|
if (propertyName !== null) orderedPropertyNames.push(propertyName);
|
|
41332
41266
|
}
|
|
41333
|
-
const sensitiveProperties = orderedPropertyNames.filter((propertyName) =>
|
|
41267
|
+
const sensitiveProperties = orderedPropertyNames.filter((propertyName) => TANSTACK_ROUTE_PROPERTY_ORDER.includes(propertyName));
|
|
41334
41268
|
let lastIndex = -1;
|
|
41335
41269
|
for (const propertyName of sensitiveProperties) {
|
|
41336
|
-
const currentIndex =
|
|
41270
|
+
const currentIndex = TANSTACK_ROUTE_PROPERTY_ORDER.indexOf(propertyName);
|
|
41337
41271
|
if (currentIndex < lastIndex) {
|
|
41338
41272
|
const expectedBefore = TANSTACK_ROUTE_PROPERTY_ORDER[lastIndex];
|
|
41339
41273
|
context.report({
|
|
@@ -41370,10 +41304,10 @@ const tanstackStartServerFnMethodOrder = defineRule({
|
|
|
41370
41304
|
} else return;
|
|
41371
41305
|
const ownMethodName = isNodeOfType(node.callee.property, "Identifier") ? node.callee.property.name : null;
|
|
41372
41306
|
if (methodNames[methodNames.length - 1] !== ownMethodName) return;
|
|
41373
|
-
const orderSensitiveMethods = methodNames.filter((name) =>
|
|
41307
|
+
const orderSensitiveMethods = methodNames.filter((name) => TANSTACK_MIDDLEWARE_METHOD_ORDER.includes(toMethodOrderToken(name)));
|
|
41374
41308
|
let lastIndex = -1;
|
|
41375
41309
|
for (const methodName of orderSensitiveMethods) {
|
|
41376
|
-
const currentIndex =
|
|
41310
|
+
const currentIndex = TANSTACK_MIDDLEWARE_METHOD_ORDER.indexOf(toMethodOrderToken(methodName));
|
|
41377
41311
|
if (currentIndex < lastIndex) {
|
|
41378
41312
|
const expectedBefore = TANSTACK_MIDDLEWARE_METHOD_ORDER[lastIndex];
|
|
41379
41313
|
context.report({
|
|
@@ -41664,14 +41598,7 @@ const getStaticPropertyName = (member) => {
|
|
|
41664
41598
|
if (member.computed && isNodeOfType(property, "Literal") && typeof property.value === "string") return property.value;
|
|
41665
41599
|
return null;
|
|
41666
41600
|
};
|
|
41667
|
-
const importInfoCache = /* @__PURE__ */ new WeakMap();
|
|
41668
41601
|
const getImportInfoForIdentifier = (identifier) => {
|
|
41669
|
-
if (importInfoCache.has(identifier)) return importInfoCache.get(identifier) ?? null;
|
|
41670
|
-
const importInfo = computeImportInfoForIdentifier(identifier);
|
|
41671
|
-
importInfoCache.set(identifier, importInfo);
|
|
41672
|
-
return importInfo;
|
|
41673
|
-
};
|
|
41674
|
-
const computeImportInfoForIdentifier = (identifier) => {
|
|
41675
41602
|
const specifier = findVariableInitializer(identifier, identifier.name)?.initializer;
|
|
41676
41603
|
if (!specifier) return null;
|
|
41677
41604
|
const declaration = specifier.parent;
|