oxlint-plugin-react-doctor 0.5.6-dev.b8170f8 → 0.5.6-dev.ed0258c
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 +0 -1
- package/dist/index.js +25 -90
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -36,7 +36,6 @@ interface ControlFlowAnalysis {
|
|
|
36
36
|
readonly cfgFor: (functionLike: EsTreeNode) => FunctionCfg | null;
|
|
37
37
|
readonly enclosingFunction: (node: EsTreeNode) => EsTreeNode | null;
|
|
38
38
|
readonly isUnconditionalFromEntry: (node: EsTreeNode) => boolean;
|
|
39
|
-
readonly dominatesExit: (node: EsTreeNode) => boolean;
|
|
40
39
|
}
|
|
41
40
|
//#endregion
|
|
42
41
|
//#region src/plugin/semantic/scope-analysis.d.ts
|
package/dist/index.js
CHANGED
|
@@ -3019,7 +3019,7 @@ const ABSTRACT_ROLES = new Set([
|
|
|
3019
3019
|
"widget",
|
|
3020
3020
|
"window"
|
|
3021
3021
|
]);
|
|
3022
|
-
const PRESENTATION_ROLES$
|
|
3022
|
+
const PRESENTATION_ROLES$1 = new Set(["presentation", "none"]);
|
|
3023
3023
|
//#endregion
|
|
3024
3024
|
//#region src/plugin/rules/a11y/aria-role.ts
|
|
3025
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}`;
|
|
@@ -4710,6 +4710,14 @@ const checkedRequiresOnchangeOrReadonly = defineRule({
|
|
|
4710
4710
|
}
|
|
4711
4711
|
});
|
|
4712
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
|
|
4713
4721
|
//#region src/plugin/utils/is-pure-event-blocker-handler.ts
|
|
4714
4722
|
const BLOCKER_METHOD_NAMES = new Set([
|
|
4715
4723
|
"stopPropagation",
|
|
@@ -4747,7 +4755,6 @@ const isPureEventBlockerHandler = (attribute) => {
|
|
|
4747
4755
|
};
|
|
4748
4756
|
//#endregion
|
|
4749
4757
|
//#region src/plugin/rules/a11y/click-events-have-key-events.ts
|
|
4750
|
-
const PRESENTATION_ROLES$1 = new Set(["presentation", "none"]);
|
|
4751
4758
|
const MESSAGE$56 = "Keyboard users can't trigger this click handler because there's no keyboard one, so add `onKeyUp`, `onKeyDown`, or `onKeyPress`.";
|
|
4752
4759
|
const KEY_HANDLERS = [
|
|
4753
4760
|
"onKeyUp",
|
|
@@ -4772,11 +4779,7 @@ const clickEventsHaveKeyEvents = defineRule({
|
|
|
4772
4779
|
if (!onClick) return;
|
|
4773
4780
|
if (isPureEventBlockerHandler(onClick)) return;
|
|
4774
4781
|
if (isHiddenFromScreenReader(node, context.settings)) return;
|
|
4775
|
-
|
|
4776
|
-
if (roleAttribute) {
|
|
4777
|
-
const roleValue = getJsxPropStringValue(roleAttribute);
|
|
4778
|
-
if (roleValue && PRESENTATION_ROLES$1.has(roleValue)) return;
|
|
4779
|
-
}
|
|
4782
|
+
if (isPresentationRole(node)) return;
|
|
4780
4783
|
if (KEY_HANDLERS.some((handler) => hasJsxPropIgnoreCase(node.attributes, handler))) return;
|
|
4781
4784
|
context.report({
|
|
4782
4785
|
node: node.name,
|
|
@@ -5747,7 +5750,7 @@ const displayName = defineRule({
|
|
|
5747
5750
|
category: "Architecture",
|
|
5748
5751
|
create: (context) => {
|
|
5749
5752
|
const settings = resolveSettings$44(context.settings);
|
|
5750
|
-
const ignoreNamed = settings.ignoreTranspilerName
|
|
5753
|
+
const ignoreNamed = !settings.ignoreTranspilerName;
|
|
5751
5754
|
const reportAt = (node) => {
|
|
5752
5755
|
context.report({
|
|
5753
5756
|
node,
|
|
@@ -8170,7 +8173,6 @@ const htmlHasLang = defineRule({
|
|
|
8170
8173
|
return { JSXOpeningElement(node) {
|
|
8171
8174
|
const tag = getElementType(node, context.settings);
|
|
8172
8175
|
if (!tagSet.has(tag)) return;
|
|
8173
|
-
const hasSpread = node.attributes.some((attribute) => isNodeOfType(attribute, "JSXSpreadAttribute"));
|
|
8174
8176
|
const lang = hasJsxPropIgnoreCase(node.attributes, "lang");
|
|
8175
8177
|
if (!lang) {
|
|
8176
8178
|
context.report({
|
|
@@ -8179,16 +8181,8 @@ const htmlHasLang = defineRule({
|
|
|
8179
8181
|
});
|
|
8180
8182
|
return;
|
|
8181
8183
|
}
|
|
8182
|
-
|
|
8183
|
-
|
|
8184
|
-
context.report({
|
|
8185
|
-
node: lang,
|
|
8186
|
-
message: MESSAGE$50
|
|
8187
|
-
});
|
|
8188
|
-
return;
|
|
8189
|
-
}
|
|
8190
|
-
if (hasSpread && !lang) context.report({
|
|
8191
|
-
node: node.name,
|
|
8184
|
+
if (evaluateLang(lang.value) === "empty") context.report({
|
|
8185
|
+
node: lang,
|
|
8192
8186
|
message: MESSAGE$50
|
|
8193
8187
|
});
|
|
8194
8188
|
} };
|
|
@@ -8902,14 +8896,6 @@ const isNonInteractiveElement = (elementType, openingElement) => {
|
|
|
8902
8896
|
//#region src/plugin/utils/is-non-interactive-role.ts
|
|
8903
8897
|
const isNonInteractiveRole = (role) => NON_INTERACTIVE_ROLES.has(role);
|
|
8904
8898
|
//#endregion
|
|
8905
|
-
//#region src/plugin/utils/is-presentation-role.ts
|
|
8906
|
-
const isPresentationRole = (openingElement) => {
|
|
8907
|
-
const roleAttribute = hasJsxPropIgnoreCase(openingElement.attributes, "role");
|
|
8908
|
-
if (!roleAttribute) return false;
|
|
8909
|
-
const value = getJsxPropStringValue(roleAttribute);
|
|
8910
|
-
return value !== null && PRESENTATION_ROLES$2.has(value);
|
|
8911
|
-
};
|
|
8912
|
-
//#endregion
|
|
8913
8899
|
//#region src/plugin/rules/a11y/interactive-supports-focus.ts
|
|
8914
8900
|
const buildTabbableMessage = (role) => `Keyboard users can't tab to this '${role}' because it isn't focusable, so add \`tabIndex={0}\`.`;
|
|
8915
8901
|
const buildFocusableMessage = (role) => `Keyboard users can't focus this '${role}' because it can't receive focus, so add \`tabIndex={0}\` or \`tabIndex={-1}\`.`;
|
|
@@ -17452,6 +17438,7 @@ const noDanger = defineRule({
|
|
|
17452
17438
|
title: "Raw HTML injection can run unsafe markup",
|
|
17453
17439
|
severity: "warn",
|
|
17454
17440
|
category: "Security",
|
|
17441
|
+
defaultEnabled: false,
|
|
17455
17442
|
recommendation: "Render trusted content as React children so attacker-controlled HTML cannot run in users' browsers.",
|
|
17456
17443
|
create: (context) => ({
|
|
17457
17444
|
JSXOpeningElement(node) {
|
|
@@ -25441,15 +25428,8 @@ const expressionContainsJsxOrCreateElement = (root) => {
|
|
|
25441
25428
|
visit(root);
|
|
25442
25429
|
return found;
|
|
25443
25430
|
};
|
|
25444
|
-
const classExtendsReactComponent$1 = (classNode) => {
|
|
25445
|
-
const superClass = classNode.superClass;
|
|
25446
|
-
if (!superClass) return false;
|
|
25447
|
-
if (isNodeOfType(superClass, "Identifier") && (superClass.name === "Component" || superClass.name === "PureComponent")) return true;
|
|
25448
|
-
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;
|
|
25449
|
-
return false;
|
|
25450
|
-
};
|
|
25451
25431
|
const isReactClassComponent = (classNode) => {
|
|
25452
|
-
if (
|
|
25432
|
+
if (isEs6Component(classNode)) return true;
|
|
25453
25433
|
return expressionContainsJsxOrCreateElement(classNode);
|
|
25454
25434
|
};
|
|
25455
25435
|
const findEnclosingComponent = (node) => {
|
|
@@ -25609,7 +25589,7 @@ const noUnstableNestedComponents = defineRule({
|
|
|
25609
25589
|
create: (context) => {
|
|
25610
25590
|
const settings = resolveSettings$8(context.settings);
|
|
25611
25591
|
const renderPropRegex = compileGlob(settings.propNamePattern);
|
|
25612
|
-
const reportCandidate = (candidateNode, reportNode
|
|
25592
|
+
const reportCandidate = (candidateNode, reportNode) => {
|
|
25613
25593
|
if (isFirstArgumentOfHocCall(candidateNode)) return;
|
|
25614
25594
|
if (isReturnOfMapCallback(candidateNode)) return;
|
|
25615
25595
|
const propInfo = isComponentDeclaredInProp(candidateNode);
|
|
@@ -25630,7 +25610,7 @@ const noUnstableNestedComponents = defineRule({
|
|
|
25630
25610
|
const inferredName = inferFunctionLikeName(node);
|
|
25631
25611
|
const propInfo = isComponentDeclaredInProp(node);
|
|
25632
25612
|
if (!(inferredName !== null && isReactComponentName(inferredName) || propInfo !== null || isObjectCallbackCandidate(node))) return;
|
|
25633
|
-
reportCandidate(node, node
|
|
25613
|
+
reportCandidate(node, node);
|
|
25634
25614
|
};
|
|
25635
25615
|
return {
|
|
25636
25616
|
FunctionDeclaration: checkFunctionLike,
|
|
@@ -25640,18 +25620,18 @@ const noUnstableNestedComponents = defineRule({
|
|
|
25640
25620
|
if (!node.id) return;
|
|
25641
25621
|
if (!isReactComponentName(node.id.name)) return;
|
|
25642
25622
|
if (!isReactClassComponent(node)) return;
|
|
25643
|
-
reportCandidate(node, node
|
|
25623
|
+
reportCandidate(node, node);
|
|
25644
25624
|
},
|
|
25645
25625
|
ClassExpression(node) {
|
|
25646
25626
|
const inferredName = node.id?.name ?? inferFunctionLikeName(node);
|
|
25647
25627
|
if (!inferredName || !isReactComponentName(inferredName)) return;
|
|
25648
25628
|
if (!isReactClassComponent(node)) return;
|
|
25649
|
-
reportCandidate(node, node
|
|
25629
|
+
reportCandidate(node, node);
|
|
25650
25630
|
},
|
|
25651
25631
|
CallExpression(node) {
|
|
25652
25632
|
if (!isHocCallee$1(node)) return;
|
|
25653
25633
|
if (!hocCallContainsComponent(node)) return;
|
|
25654
|
-
reportCandidate(node, node
|
|
25634
|
+
reportCandidate(node, node);
|
|
25655
25635
|
}
|
|
25656
25636
|
};
|
|
25657
25637
|
}
|
|
@@ -26091,13 +26071,6 @@ const skipTsExpression = (expression) => {
|
|
|
26091
26071
|
if (expression.type === "TSAsExpression" || expression.type === "TSSatisfiesExpression" || expression.type === "TSNonNullExpression") return skipTsExpression(expression.expression);
|
|
26092
26072
|
return expression;
|
|
26093
26073
|
};
|
|
26094
|
-
const classExtendsReactComponent = (classNode) => {
|
|
26095
|
-
const superClass = classNode.superClass;
|
|
26096
|
-
if (!superClass) return false;
|
|
26097
|
-
if (isNodeOfType(superClass, "Identifier") && (superClass.name === "Component" || superClass.name === "PureComponent")) return true;
|
|
26098
|
-
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;
|
|
26099
|
-
return false;
|
|
26100
|
-
};
|
|
26101
26074
|
const isReactCreateContext = (initializer) => {
|
|
26102
26075
|
if (!initializer) return false;
|
|
26103
26076
|
const expression = skipTsExpression(initializer);
|
|
@@ -26288,7 +26261,7 @@ const onlyExportComponents = defineRule({
|
|
|
26288
26261
|
if (stripped.id) {
|
|
26289
26262
|
const idNode = stripped.id;
|
|
26290
26263
|
isExportedNodeIds.add(stripped);
|
|
26291
|
-
if (isReactComponentName(idNode.name) &&
|
|
26264
|
+
if (isReactComponentName(idNode.name) && isEs6Component(stripped)) hasReactExport = true;
|
|
26292
26265
|
else exports.push({
|
|
26293
26266
|
kind: "non-component",
|
|
26294
26267
|
reportNode: idNode
|
|
@@ -26348,7 +26321,7 @@ const onlyExportComponents = defineRule({
|
|
|
26348
26321
|
exports.push(classifyExport(declaration.id.name, declaration.id, true, null, state));
|
|
26349
26322
|
} else if (isNodeOfType(declaration, "ClassDeclaration") && declaration.id) {
|
|
26350
26323
|
isExportedNodeIds.add(declaration);
|
|
26351
|
-
if (isReactComponentName(declaration.id.name) &&
|
|
26324
|
+
if (isReactComponentName(declaration.id.name) && isEs6Component(declaration)) exports.push({ kind: "react-component" });
|
|
26352
26325
|
else exports.push({
|
|
26353
26326
|
kind: "non-component",
|
|
26354
26327
|
reportNode: declaration.id
|
|
@@ -42304,32 +42277,6 @@ const computeUnconditionalSet = (cfg) => {
|
|
|
42304
42277
|
}
|
|
42305
42278
|
return unconditional;
|
|
42306
42279
|
};
|
|
42307
|
-
const computeDominatesExit = (cfg) => {
|
|
42308
|
-
const reachableToExit = /* @__PURE__ */ new Set();
|
|
42309
|
-
const queue = [cfg.exit];
|
|
42310
|
-
while (queue.length > 0) {
|
|
42311
|
-
const block = queue.shift();
|
|
42312
|
-
if (reachableToExit.has(block)) continue;
|
|
42313
|
-
reachableToExit.add(block);
|
|
42314
|
-
for (const edge of block.predecessors) queue.push(edge.from);
|
|
42315
|
-
}
|
|
42316
|
-
const dominatesExit = /* @__PURE__ */ new Set();
|
|
42317
|
-
const visit = (block) => {
|
|
42318
|
-
if (block === cfg.exit) return true;
|
|
42319
|
-
if (dominatesExit.has(block)) return true;
|
|
42320
|
-
if (block.successors.length === 0) return false;
|
|
42321
|
-
dominatesExit.add(block);
|
|
42322
|
-
let allReach = true;
|
|
42323
|
-
for (const edge of block.successors) if (!visit(edge.to)) {
|
|
42324
|
-
allReach = false;
|
|
42325
|
-
break;
|
|
42326
|
-
}
|
|
42327
|
-
if (!allReach) dominatesExit.delete(block);
|
|
42328
|
-
return allReach;
|
|
42329
|
-
};
|
|
42330
|
-
for (const block of cfg.blocks) visit(block);
|
|
42331
|
-
return dominatesExit;
|
|
42332
|
-
};
|
|
42333
42280
|
const analyzeControlFlow = (program) => {
|
|
42334
42281
|
nextBlockId = 0;
|
|
42335
42282
|
const functionCfgs = /* @__PURE__ */ new Map();
|
|
@@ -42337,8 +42284,7 @@ const analyzeControlFlow = (program) => {
|
|
|
42337
42284
|
const cfg = buildFunctionCfg(functionNode, body);
|
|
42338
42285
|
functionCfgs.set(functionNode, {
|
|
42339
42286
|
cfg,
|
|
42340
|
-
unconditionalSet: computeUnconditionalSet(cfg)
|
|
42341
|
-
dominatesExitSet: computeDominatesExit(cfg)
|
|
42287
|
+
unconditionalSet: computeUnconditionalSet(cfg)
|
|
42342
42288
|
});
|
|
42343
42289
|
};
|
|
42344
42290
|
if (isNodeOfType(program, "Program")) buildFor(program, {
|
|
@@ -42381,20 +42327,10 @@ const analyzeControlFlow = (program) => {
|
|
|
42381
42327
|
if (!block) return true;
|
|
42382
42328
|
return entry.unconditionalSet.has(block);
|
|
42383
42329
|
};
|
|
42384
|
-
const dominatesExit = (node) => {
|
|
42385
|
-
const owner = enclosingFunction(node);
|
|
42386
|
-
if (!owner) return true;
|
|
42387
|
-
const entry = functionCfgs.get(owner);
|
|
42388
|
-
if (!entry) return true;
|
|
42389
|
-
const block = entry.cfg.blockOf(node);
|
|
42390
|
-
if (!block) return true;
|
|
42391
|
-
return entry.dominatesExitSet.has(block);
|
|
42392
|
-
};
|
|
42393
42330
|
return {
|
|
42394
42331
|
cfgFor,
|
|
42395
42332
|
enclosingFunction,
|
|
42396
|
-
isUnconditionalFromEntry
|
|
42397
|
-
dominatesExit
|
|
42333
|
+
isUnconditionalFromEntry
|
|
42398
42334
|
};
|
|
42399
42335
|
};
|
|
42400
42336
|
//#endregion
|
|
@@ -42419,8 +42355,7 @@ const buildFallbackScopes = () => ({
|
|
|
42419
42355
|
const FALLBACK_CFG = {
|
|
42420
42356
|
cfgFor: () => null,
|
|
42421
42357
|
enclosingFunction: () => null,
|
|
42422
|
-
isUnconditionalFromEntry: () => false
|
|
42423
|
-
dominatesExit: () => false
|
|
42358
|
+
isUnconditionalFromEntry: () => false
|
|
42424
42359
|
};
|
|
42425
42360
|
const wrapWithSemanticContext = (rule) => ({
|
|
42426
42361
|
...rule,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "oxlint-plugin-react-doctor",
|
|
3
|
-
"version": "0.5.6-dev.
|
|
3
|
+
"version": "0.5.6-dev.ed0258c",
|
|
4
4
|
"description": "oxlint plugin for React Doctor: diagnose React codebases for security, performance, correctness, accessibility, bundle-size, and architecture issues",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"accessibility",
|