oxlint-plugin-react-doctor 0.2.11-dev.f4035fc → 0.2.12-dev.269ca17
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 +136 -0
- package/dist/index.js +470 -1
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -5542,6 +5542,74 @@ declare const REACT_DOCTOR_RULES: readonly [{
|
|
|
5542
5542
|
readonly recommendation?: string;
|
|
5543
5543
|
readonly create: (context: RuleContext) => RuleVisitors;
|
|
5544
5544
|
};
|
|
5545
|
+
}, {
|
|
5546
|
+
readonly key: "react-doctor/zod-v4-no-deprecated-error-apis";
|
|
5547
|
+
readonly id: "zod-v4-no-deprecated-error-apis";
|
|
5548
|
+
readonly source: "react-doctor";
|
|
5549
|
+
readonly originallyExternal: false;
|
|
5550
|
+
readonly rule: {
|
|
5551
|
+
readonly framework: "global";
|
|
5552
|
+
readonly category: "Architecture";
|
|
5553
|
+
readonly id: string;
|
|
5554
|
+
readonly severity: RuleSeverity;
|
|
5555
|
+
readonly requires?: ReadonlyArray<string>;
|
|
5556
|
+
readonly disabledBy?: ReadonlyArray<string>;
|
|
5557
|
+
readonly tags?: ReadonlyArray<string>;
|
|
5558
|
+
readonly defaultEnabled?: boolean;
|
|
5559
|
+
readonly recommendation?: string;
|
|
5560
|
+
readonly create: (context: RuleContext) => RuleVisitors;
|
|
5561
|
+
};
|
|
5562
|
+
}, {
|
|
5563
|
+
readonly key: "react-doctor/zod-v4-no-deprecated-error-customization";
|
|
5564
|
+
readonly id: "zod-v4-no-deprecated-error-customization";
|
|
5565
|
+
readonly source: "react-doctor";
|
|
5566
|
+
readonly originallyExternal: false;
|
|
5567
|
+
readonly rule: {
|
|
5568
|
+
readonly framework: "global";
|
|
5569
|
+
readonly category: "Architecture";
|
|
5570
|
+
readonly id: string;
|
|
5571
|
+
readonly severity: RuleSeverity;
|
|
5572
|
+
readonly requires?: ReadonlyArray<string>;
|
|
5573
|
+
readonly disabledBy?: ReadonlyArray<string>;
|
|
5574
|
+
readonly tags?: ReadonlyArray<string>;
|
|
5575
|
+
readonly defaultEnabled?: boolean;
|
|
5576
|
+
readonly recommendation?: string;
|
|
5577
|
+
readonly create: (context: RuleContext) => RuleVisitors;
|
|
5578
|
+
};
|
|
5579
|
+
}, {
|
|
5580
|
+
readonly key: "react-doctor/zod-v4-no-deprecated-schema-apis";
|
|
5581
|
+
readonly id: "zod-v4-no-deprecated-schema-apis";
|
|
5582
|
+
readonly source: "react-doctor";
|
|
5583
|
+
readonly originallyExternal: false;
|
|
5584
|
+
readonly rule: {
|
|
5585
|
+
readonly framework: "global";
|
|
5586
|
+
readonly category: "Architecture";
|
|
5587
|
+
readonly id: string;
|
|
5588
|
+
readonly severity: RuleSeverity;
|
|
5589
|
+
readonly requires?: ReadonlyArray<string>;
|
|
5590
|
+
readonly disabledBy?: ReadonlyArray<string>;
|
|
5591
|
+
readonly tags?: ReadonlyArray<string>;
|
|
5592
|
+
readonly defaultEnabled?: boolean;
|
|
5593
|
+
readonly recommendation?: string;
|
|
5594
|
+
readonly create: (context: RuleContext) => RuleVisitors;
|
|
5595
|
+
};
|
|
5596
|
+
}, {
|
|
5597
|
+
readonly key: "react-doctor/zod-v4-prefer-top-level-string-formats";
|
|
5598
|
+
readonly id: "zod-v4-prefer-top-level-string-formats";
|
|
5599
|
+
readonly source: "react-doctor";
|
|
5600
|
+
readonly originallyExternal: false;
|
|
5601
|
+
readonly rule: {
|
|
5602
|
+
readonly framework: "global";
|
|
5603
|
+
readonly category: "Architecture";
|
|
5604
|
+
readonly id: string;
|
|
5605
|
+
readonly severity: RuleSeverity;
|
|
5606
|
+
readonly requires?: ReadonlyArray<string>;
|
|
5607
|
+
readonly disabledBy?: ReadonlyArray<string>;
|
|
5608
|
+
readonly tags?: ReadonlyArray<string>;
|
|
5609
|
+
readonly defaultEnabled?: boolean;
|
|
5610
|
+
readonly recommendation?: string;
|
|
5611
|
+
readonly create: (context: RuleContext) => RuleVisitors;
|
|
5612
|
+
};
|
|
5545
5613
|
}];
|
|
5546
5614
|
declare const EXTERNAL_RULES: readonly [{
|
|
5547
5615
|
readonly key: "react-hooks-js/set-state-in-render";
|
|
@@ -11014,6 +11082,74 @@ declare const RULES: readonly [{
|
|
|
11014
11082
|
readonly recommendation?: string;
|
|
11015
11083
|
readonly create: (context: RuleContext) => RuleVisitors;
|
|
11016
11084
|
};
|
|
11085
|
+
}, {
|
|
11086
|
+
readonly key: "react-doctor/zod-v4-no-deprecated-error-apis";
|
|
11087
|
+
readonly id: "zod-v4-no-deprecated-error-apis";
|
|
11088
|
+
readonly source: "react-doctor";
|
|
11089
|
+
readonly originallyExternal: false;
|
|
11090
|
+
readonly rule: {
|
|
11091
|
+
readonly framework: "global";
|
|
11092
|
+
readonly category: "Architecture";
|
|
11093
|
+
readonly id: string;
|
|
11094
|
+
readonly severity: RuleSeverity;
|
|
11095
|
+
readonly requires?: ReadonlyArray<string>;
|
|
11096
|
+
readonly disabledBy?: ReadonlyArray<string>;
|
|
11097
|
+
readonly tags?: ReadonlyArray<string>;
|
|
11098
|
+
readonly defaultEnabled?: boolean;
|
|
11099
|
+
readonly recommendation?: string;
|
|
11100
|
+
readonly create: (context: RuleContext) => RuleVisitors;
|
|
11101
|
+
};
|
|
11102
|
+
}, {
|
|
11103
|
+
readonly key: "react-doctor/zod-v4-no-deprecated-error-customization";
|
|
11104
|
+
readonly id: "zod-v4-no-deprecated-error-customization";
|
|
11105
|
+
readonly source: "react-doctor";
|
|
11106
|
+
readonly originallyExternal: false;
|
|
11107
|
+
readonly rule: {
|
|
11108
|
+
readonly framework: "global";
|
|
11109
|
+
readonly category: "Architecture";
|
|
11110
|
+
readonly id: string;
|
|
11111
|
+
readonly severity: RuleSeverity;
|
|
11112
|
+
readonly requires?: ReadonlyArray<string>;
|
|
11113
|
+
readonly disabledBy?: ReadonlyArray<string>;
|
|
11114
|
+
readonly tags?: ReadonlyArray<string>;
|
|
11115
|
+
readonly defaultEnabled?: boolean;
|
|
11116
|
+
readonly recommendation?: string;
|
|
11117
|
+
readonly create: (context: RuleContext) => RuleVisitors;
|
|
11118
|
+
};
|
|
11119
|
+
}, {
|
|
11120
|
+
readonly key: "react-doctor/zod-v4-no-deprecated-schema-apis";
|
|
11121
|
+
readonly id: "zod-v4-no-deprecated-schema-apis";
|
|
11122
|
+
readonly source: "react-doctor";
|
|
11123
|
+
readonly originallyExternal: false;
|
|
11124
|
+
readonly rule: {
|
|
11125
|
+
readonly framework: "global";
|
|
11126
|
+
readonly category: "Architecture";
|
|
11127
|
+
readonly id: string;
|
|
11128
|
+
readonly severity: RuleSeverity;
|
|
11129
|
+
readonly requires?: ReadonlyArray<string>;
|
|
11130
|
+
readonly disabledBy?: ReadonlyArray<string>;
|
|
11131
|
+
readonly tags?: ReadonlyArray<string>;
|
|
11132
|
+
readonly defaultEnabled?: boolean;
|
|
11133
|
+
readonly recommendation?: string;
|
|
11134
|
+
readonly create: (context: RuleContext) => RuleVisitors;
|
|
11135
|
+
};
|
|
11136
|
+
}, {
|
|
11137
|
+
readonly key: "react-doctor/zod-v4-prefer-top-level-string-formats";
|
|
11138
|
+
readonly id: "zod-v4-prefer-top-level-string-formats";
|
|
11139
|
+
readonly source: "react-doctor";
|
|
11140
|
+
readonly originallyExternal: false;
|
|
11141
|
+
readonly rule: {
|
|
11142
|
+
readonly framework: "global";
|
|
11143
|
+
readonly category: "Architecture";
|
|
11144
|
+
readonly id: string;
|
|
11145
|
+
readonly severity: RuleSeverity;
|
|
11146
|
+
readonly requires?: ReadonlyArray<string>;
|
|
11147
|
+
readonly disabledBy?: ReadonlyArray<string>;
|
|
11148
|
+
readonly tags?: ReadonlyArray<string>;
|
|
11149
|
+
readonly defaultEnabled?: boolean;
|
|
11150
|
+
readonly recommendation?: string;
|
|
11151
|
+
readonly create: (context: RuleContext) => RuleVisitors;
|
|
11152
|
+
};
|
|
11017
11153
|
}, {
|
|
11018
11154
|
readonly key: "react-hooks-js/set-state-in-render";
|
|
11019
11155
|
readonly source: "react-compiler";
|
package/dist/index.js
CHANGED
|
@@ -26668,6 +26668,7 @@ const REACT_NATIVE_TEXT_COMPONENT_KEYWORDS = new Set([
|
|
|
26668
26668
|
"Description",
|
|
26669
26669
|
"Body"
|
|
26670
26670
|
]);
|
|
26671
|
+
const REACT_NATIVE_TEXT_TRANSPARENT_COMPONENTS = new Set(["fbt", "fbs"]);
|
|
26671
26672
|
const DEPRECATED_RN_MODULE_REPLACEMENTS = new Map([
|
|
26672
26673
|
["AsyncStorage", "@react-native-async-storage/async-storage"],
|
|
26673
26674
|
["Picker", "@react-native-picker/picker"],
|
|
@@ -27500,10 +27501,29 @@ const getRawTextDescription = (child) => {
|
|
|
27500
27501
|
}
|
|
27501
27502
|
return "text content";
|
|
27502
27503
|
};
|
|
27504
|
+
const resolveTextBoundaryName = (openingElement) => {
|
|
27505
|
+
if (isNodeOfType(openingElement.name, "JSXNamespacedName")) return openingElement.name.namespace.name;
|
|
27506
|
+
return resolveJsxElementName(openingElement);
|
|
27507
|
+
};
|
|
27503
27508
|
const isTextHandlingComponent = (elementName) => {
|
|
27504
27509
|
if (REACT_NATIVE_TEXT_COMPONENTS.has(elementName)) return true;
|
|
27505
27510
|
return [...REACT_NATIVE_TEXT_COMPONENT_KEYWORDS].some((keyword) => elementName.includes(keyword));
|
|
27506
27511
|
};
|
|
27512
|
+
const isTransparentTextWrapper = (elementName) => elementName !== null && REACT_NATIVE_TEXT_TRANSPARENT_COMPONENTS.has(elementName);
|
|
27513
|
+
const isInsideTextHandlingComponent = (node) => {
|
|
27514
|
+
let parentNode = node.parent;
|
|
27515
|
+
while (parentNode) {
|
|
27516
|
+
if (!isNodeOfType(parentNode, "JSXElement")) {
|
|
27517
|
+
parentNode = parentNode.parent;
|
|
27518
|
+
continue;
|
|
27519
|
+
}
|
|
27520
|
+
const parentName = resolveTextBoundaryName(parentNode.openingElement);
|
|
27521
|
+
if (parentName && isTextHandlingComponent(parentName)) return true;
|
|
27522
|
+
if (!isTransparentTextWrapper(parentName)) return false;
|
|
27523
|
+
parentNode = parentNode.parent;
|
|
27524
|
+
}
|
|
27525
|
+
return false;
|
|
27526
|
+
};
|
|
27507
27527
|
const rnNoRawText = defineRule({
|
|
27508
27528
|
id: "rn-no-raw-text",
|
|
27509
27529
|
requires: ["react-native"],
|
|
@@ -27517,9 +27537,10 @@ const rnNoRawText = defineRule({
|
|
|
27517
27537
|
},
|
|
27518
27538
|
JSXElement(node) {
|
|
27519
27539
|
if (isDomComponentFile) return;
|
|
27520
|
-
const elementName =
|
|
27540
|
+
const elementName = resolveTextBoundaryName(node.openingElement);
|
|
27521
27541
|
if (elementName && isTextHandlingComponent(elementName)) return;
|
|
27522
27542
|
if (isInsidePlatformOsWebBranch(node)) return;
|
|
27543
|
+
if (isTransparentTextWrapper(elementName) && isInsideTextHandlingComponent(node)) return;
|
|
27523
27544
|
for (const child of node.children ?? []) {
|
|
27524
27545
|
if (!isRawTextContent(child)) continue;
|
|
27525
27546
|
context.report({
|
|
@@ -33358,6 +33379,410 @@ const voidDomElementsNoChildren = defineRule({
|
|
|
33358
33379
|
})
|
|
33359
33380
|
});
|
|
33360
33381
|
//#endregion
|
|
33382
|
+
//#region src/plugin/rules/zod/utils/zod-ast.ts
|
|
33383
|
+
const ZOD_MODULE = "zod";
|
|
33384
|
+
const getStaticPropertyName = (member) => {
|
|
33385
|
+
const property = member.property;
|
|
33386
|
+
if (!member.computed && isNodeOfType(property, "Identifier")) return property.name;
|
|
33387
|
+
if (member.computed && isNodeOfType(property, "Literal") && typeof property.value === "string") return property.value;
|
|
33388
|
+
return null;
|
|
33389
|
+
};
|
|
33390
|
+
const getImportInfoForIdentifier = (identifier) => {
|
|
33391
|
+
const specifier = findVariableInitializer(identifier, identifier.name)?.initializer;
|
|
33392
|
+
if (!specifier) return null;
|
|
33393
|
+
const declaration = specifier.parent;
|
|
33394
|
+
if (!declaration || !isNodeOfType(declaration, "ImportDeclaration")) return null;
|
|
33395
|
+
if (declaration.source?.value !== ZOD_MODULE) return null;
|
|
33396
|
+
if (isNodeOfType(specifier, "ImportNamespaceSpecifier")) return {
|
|
33397
|
+
imported: null,
|
|
33398
|
+
isDefault: false,
|
|
33399
|
+
isNamespace: true
|
|
33400
|
+
};
|
|
33401
|
+
if (isNodeOfType(specifier, "ImportDefaultSpecifier")) return {
|
|
33402
|
+
imported: null,
|
|
33403
|
+
isDefault: true,
|
|
33404
|
+
isNamespace: false
|
|
33405
|
+
};
|
|
33406
|
+
if (isNodeOfType(specifier, "ImportSpecifier")) {
|
|
33407
|
+
const imported = specifier.imported;
|
|
33408
|
+
if (isNodeOfType(imported, "Identifier")) return {
|
|
33409
|
+
imported: imported.name,
|
|
33410
|
+
isDefault: false,
|
|
33411
|
+
isNamespace: false
|
|
33412
|
+
};
|
|
33413
|
+
if (isNodeOfType(imported, "Literal") && typeof imported.value === "string") return {
|
|
33414
|
+
imported: imported.value,
|
|
33415
|
+
isDefault: false,
|
|
33416
|
+
isNamespace: false
|
|
33417
|
+
};
|
|
33418
|
+
}
|
|
33419
|
+
return null;
|
|
33420
|
+
};
|
|
33421
|
+
const isZodNamespaceIdentifier = (node) => {
|
|
33422
|
+
const inner = stripParenExpression(node);
|
|
33423
|
+
if (!isNodeOfType(inner, "Identifier")) return false;
|
|
33424
|
+
const info = getImportInfoForIdentifier(inner);
|
|
33425
|
+
return Boolean(info && (info.isNamespace || info.isDefault || info.imported === "z"));
|
|
33426
|
+
};
|
|
33427
|
+
const getZodNamedImport = (node) => {
|
|
33428
|
+
const inner = stripParenExpression(node);
|
|
33429
|
+
if (!isNodeOfType(inner, "Identifier")) return null;
|
|
33430
|
+
const info = getImportInfoForIdentifier(inner);
|
|
33431
|
+
if (!info || info.isNamespace || info.isDefault) return null;
|
|
33432
|
+
return info.imported;
|
|
33433
|
+
};
|
|
33434
|
+
const getZodNamespaceMemberName = (node) => {
|
|
33435
|
+
const inner = stripParenExpression(node);
|
|
33436
|
+
if (!isNodeOfType(inner, "MemberExpression")) return null;
|
|
33437
|
+
if (!isZodNamespaceIdentifier(inner.object)) return null;
|
|
33438
|
+
return getStaticPropertyName(inner);
|
|
33439
|
+
};
|
|
33440
|
+
const isZodFactoryCall = (callExpression, factoryNames) => {
|
|
33441
|
+
const factoryName = getZodFactoryCallName(callExpression);
|
|
33442
|
+
return factoryName !== null && factoryNames.has(factoryName);
|
|
33443
|
+
};
|
|
33444
|
+
const getZodFactoryCallName = (callExpression) => {
|
|
33445
|
+
const callee = stripParenExpression(callExpression.callee);
|
|
33446
|
+
if (isNodeOfType(callee, "Identifier")) return getZodNamedImport(callee);
|
|
33447
|
+
if (!isNodeOfType(callee, "MemberExpression")) return null;
|
|
33448
|
+
const memberName = getStaticPropertyName(callee);
|
|
33449
|
+
if (memberName === null) return null;
|
|
33450
|
+
if (!isZodNamespaceIdentifier(callee.object)) return null;
|
|
33451
|
+
return memberName;
|
|
33452
|
+
};
|
|
33453
|
+
const getMethodCall = (callExpression) => {
|
|
33454
|
+
const callee = stripParenExpression(callExpression.callee);
|
|
33455
|
+
if (!isNodeOfType(callee, "MemberExpression")) return null;
|
|
33456
|
+
const methodName = getStaticPropertyName(callee);
|
|
33457
|
+
if (!methodName) return null;
|
|
33458
|
+
return {
|
|
33459
|
+
methodName,
|
|
33460
|
+
receiver: callee.object
|
|
33461
|
+
};
|
|
33462
|
+
};
|
|
33463
|
+
const isDirectMethodCallOnZodFactory = (callExpression, factoryNames, methodNames) => {
|
|
33464
|
+
const methodCall = getMethodCall(callExpression);
|
|
33465
|
+
if (!methodCall || !methodNames.has(methodCall.methodName)) return false;
|
|
33466
|
+
const receiver = stripParenExpression(methodCall.receiver);
|
|
33467
|
+
return isNodeOfType(receiver, "CallExpression") && isZodFactoryCall(receiver, factoryNames);
|
|
33468
|
+
};
|
|
33469
|
+
const isObjectExpressionWithAnyProperty = (node, propertyNames) => {
|
|
33470
|
+
if (!node) return false;
|
|
33471
|
+
const inner = stripParenExpression(node);
|
|
33472
|
+
if (!isNodeOfType(inner, "ObjectExpression")) return false;
|
|
33473
|
+
return inner.properties.some((property) => {
|
|
33474
|
+
if (!isNodeOfType(property, "Property")) return false;
|
|
33475
|
+
const key = property.key;
|
|
33476
|
+
if (isNodeOfType(key, "Identifier")) return propertyNames.has(key.name);
|
|
33477
|
+
return isNodeOfType(key, "Literal") && typeof key.value === "string" && propertyNames.has(key.value);
|
|
33478
|
+
});
|
|
33479
|
+
};
|
|
33480
|
+
//#endregion
|
|
33481
|
+
//#region src/plugin/rules/zod/zod-v4-no-deprecated-error-apis.ts
|
|
33482
|
+
const DEPRECATED_ZOD_ERROR_MEMBERS = new Set([
|
|
33483
|
+
"addIssue",
|
|
33484
|
+
"addIssues",
|
|
33485
|
+
"errors",
|
|
33486
|
+
"flatten",
|
|
33487
|
+
"formErrors",
|
|
33488
|
+
"format"
|
|
33489
|
+
]);
|
|
33490
|
+
const ZOD_ERROR_API_MESSAGE = "Zod 4 removes or deprecates this ZodError API; use `error.issues` or the new top-level error formatting helpers.";
|
|
33491
|
+
const isZodErrorReference = (node) => {
|
|
33492
|
+
const inner = stripParenExpression(node);
|
|
33493
|
+
if (isNodeOfType(inner, "Identifier")) return getZodNamedImport(inner) === "ZodError";
|
|
33494
|
+
if (!isNodeOfType(inner, "MemberExpression")) return false;
|
|
33495
|
+
return getZodNamespaceMemberName(inner) === "ZodError";
|
|
33496
|
+
};
|
|
33497
|
+
const isDirectZodErrorValue = (node) => {
|
|
33498
|
+
const inner = stripParenExpression(node);
|
|
33499
|
+
if (isNodeOfType(inner, "NewExpression")) return isZodErrorReference(inner.callee);
|
|
33500
|
+
if (!isNodeOfType(inner, "CallExpression")) return false;
|
|
33501
|
+
const methodCall = getMethodCall(inner);
|
|
33502
|
+
return methodCall?.methodName === "create" && isZodErrorReference(methodCall.receiver);
|
|
33503
|
+
};
|
|
33504
|
+
const isDeprecatedZodErrorMemberAccess = (node) => {
|
|
33505
|
+
if (!isNodeOfType(node, "MemberExpression")) return false;
|
|
33506
|
+
const memberExpression = node;
|
|
33507
|
+
const propertyName = getStaticPropertyName(memberExpression);
|
|
33508
|
+
return propertyName !== null && DEPRECATED_ZOD_ERROR_MEMBERS.has(propertyName) && isDirectZodErrorValue(memberExpression.object);
|
|
33509
|
+
};
|
|
33510
|
+
const isZodErrorCreateCall = (callExpression) => {
|
|
33511
|
+
const methodCall = getMethodCall(callExpression);
|
|
33512
|
+
return methodCall?.methodName === "create" && isZodErrorReference(methodCall.receiver);
|
|
33513
|
+
};
|
|
33514
|
+
const isReceiverOfDeprecatedZodErrorMember = (callExpression) => {
|
|
33515
|
+
const parent = callExpression.parent;
|
|
33516
|
+
if (!parent || !isNodeOfType(parent, "MemberExpression")) return false;
|
|
33517
|
+
if (stripParenExpression(parent.object) !== callExpression) return false;
|
|
33518
|
+
return isDeprecatedZodErrorMemberAccess(parent);
|
|
33519
|
+
};
|
|
33520
|
+
const zodV4NoDeprecatedErrorApis = defineRule({
|
|
33521
|
+
id: "zod-v4-no-deprecated-error-apis",
|
|
33522
|
+
requires: ["zod:4"],
|
|
33523
|
+
tags: ["migration-hint"],
|
|
33524
|
+
severity: "warn",
|
|
33525
|
+
recommendation: "Replace deprecated ZodError helpers with the Zod 4 functions: `z.treeifyError()`, `z.flattenError()`, `z.prettifyError()`, or direct `error.issues` access.",
|
|
33526
|
+
create: (context) => ({
|
|
33527
|
+
CallExpression(node) {
|
|
33528
|
+
if (isZodErrorCreateCall(node) && isReceiverOfDeprecatedZodErrorMember(node)) return;
|
|
33529
|
+
if (!isZodErrorCreateCall(node) && !isDeprecatedZodErrorMemberAccess(node.callee)) return;
|
|
33530
|
+
context.report({
|
|
33531
|
+
node,
|
|
33532
|
+
message: ZOD_ERROR_API_MESSAGE
|
|
33533
|
+
});
|
|
33534
|
+
},
|
|
33535
|
+
MemberExpression(node) {
|
|
33536
|
+
const parent = node.parent;
|
|
33537
|
+
if (parent && isNodeOfType(parent, "CallExpression") && stripParenExpression(parent.callee) === node) return;
|
|
33538
|
+
if (!isDeprecatedZodErrorMemberAccess(node)) return;
|
|
33539
|
+
context.report({
|
|
33540
|
+
node,
|
|
33541
|
+
message: ZOD_ERROR_API_MESSAGE
|
|
33542
|
+
});
|
|
33543
|
+
}
|
|
33544
|
+
})
|
|
33545
|
+
});
|
|
33546
|
+
//#endregion
|
|
33547
|
+
//#region src/plugin/rules/zod/zod-v4-no-deprecated-error-customization.ts
|
|
33548
|
+
const ZOD_FACTORIES_WITH_ERROR_PARAMS = new Set([
|
|
33549
|
+
"any",
|
|
33550
|
+
"array",
|
|
33551
|
+
"bigint",
|
|
33552
|
+
"boolean",
|
|
33553
|
+
"date",
|
|
33554
|
+
"enum",
|
|
33555
|
+
"literal",
|
|
33556
|
+
"map",
|
|
33557
|
+
"nativeEnum",
|
|
33558
|
+
"never",
|
|
33559
|
+
"null",
|
|
33560
|
+
"number",
|
|
33561
|
+
"object",
|
|
33562
|
+
"record",
|
|
33563
|
+
"set",
|
|
33564
|
+
"string",
|
|
33565
|
+
"tuple",
|
|
33566
|
+
"undefined",
|
|
33567
|
+
"union",
|
|
33568
|
+
"unknown",
|
|
33569
|
+
"void"
|
|
33570
|
+
]);
|
|
33571
|
+
const DROPPED_ERROR_OPTION_PROPERTIES = new Set([
|
|
33572
|
+
"errorMap",
|
|
33573
|
+
"invalid_type_error",
|
|
33574
|
+
"required_error"
|
|
33575
|
+
]);
|
|
33576
|
+
const FACTORIES_WITH_LEGACY_FIRST_ARG_MESSAGE = new Set([
|
|
33577
|
+
"bigint",
|
|
33578
|
+
"boolean",
|
|
33579
|
+
"date",
|
|
33580
|
+
"number",
|
|
33581
|
+
"string"
|
|
33582
|
+
]);
|
|
33583
|
+
const ERROR_MAP_PROPERTY = new Set(["errorMap"]);
|
|
33584
|
+
const PARSE_METHODS = new Set([
|
|
33585
|
+
"parse",
|
|
33586
|
+
"safeParse",
|
|
33587
|
+
"parseAsync",
|
|
33588
|
+
"safeParseAsync"
|
|
33589
|
+
]);
|
|
33590
|
+
const firstArgumentIsMessageString = (callExpression) => {
|
|
33591
|
+
const firstArgument = callExpression.arguments[0];
|
|
33592
|
+
const inner = firstArgument ? stripParenExpression(firstArgument) : null;
|
|
33593
|
+
return Boolean(inner && isNodeOfType(inner, "Literal") && typeof inner.value === "string");
|
|
33594
|
+
};
|
|
33595
|
+
const factoryUsesDeprecatedErrorParameter = (callExpression) => {
|
|
33596
|
+
const factoryName = getZodFactoryCallName(callExpression);
|
|
33597
|
+
if (factoryName === null || !ZOD_FACTORIES_WITH_ERROR_PARAMS.has(factoryName)) return false;
|
|
33598
|
+
return FACTORIES_WITH_LEGACY_FIRST_ARG_MESSAGE.has(factoryName) && firstArgumentIsMessageString(callExpression) || callExpression.arguments.some((argument) => isObjectExpressionWithAnyProperty(argument, DROPPED_ERROR_OPTION_PROPERTIES));
|
|
33599
|
+
};
|
|
33600
|
+
const parseCallUsesErrorMap = (callExpression) => {
|
|
33601
|
+
const methodCall = getMethodCall(callExpression);
|
|
33602
|
+
if (!methodCall || !PARSE_METHODS.has(methodCall.methodName)) return false;
|
|
33603
|
+
const receiver = stripParenExpression(methodCall.receiver);
|
|
33604
|
+
if (!isNodeOfType(receiver, "CallExpression")) return false;
|
|
33605
|
+
if (!isZodFactoryCall(receiver, ZOD_FACTORIES_WITH_ERROR_PARAMS)) return false;
|
|
33606
|
+
return isObjectExpressionWithAnyProperty(callExpression.arguments[1], ERROR_MAP_PROPERTY);
|
|
33607
|
+
};
|
|
33608
|
+
const zodV4NoDeprecatedErrorCustomization = defineRule({
|
|
33609
|
+
id: "zod-v4-no-deprecated-error-customization",
|
|
33610
|
+
requires: ["zod:4"],
|
|
33611
|
+
tags: ["migration-hint"],
|
|
33612
|
+
severity: "warn",
|
|
33613
|
+
recommendation: "Use Zod 4's unified `{ error }` callback/object customization instead of string message parameters, `invalid_type_error`, `required_error`, or `errorMap`.",
|
|
33614
|
+
create: (context) => ({ CallExpression(node) {
|
|
33615
|
+
if (!factoryUsesDeprecatedErrorParameter(node) && !parseCallUsesErrorMap(node)) return;
|
|
33616
|
+
context.report({
|
|
33617
|
+
node,
|
|
33618
|
+
message: "Zod 4 replaces message parameters, `invalid_type_error`, `required_error`, and `errorMap` with the unified `error` API."
|
|
33619
|
+
});
|
|
33620
|
+
} })
|
|
33621
|
+
});
|
|
33622
|
+
//#endregion
|
|
33623
|
+
//#region src/plugin/rules/zod/zod-v4-no-deprecated-schema-apis.ts
|
|
33624
|
+
const OBJECT_FACTORY = new Set(["object"]);
|
|
33625
|
+
const OBJECT_METHODS = new Set([
|
|
33626
|
+
"deepPartial",
|
|
33627
|
+
"merge",
|
|
33628
|
+
"nonstrict",
|
|
33629
|
+
"passthrough",
|
|
33630
|
+
"strict",
|
|
33631
|
+
"strip"
|
|
33632
|
+
]);
|
|
33633
|
+
const NUMBER_FACTORY = new Set(["number"]);
|
|
33634
|
+
const NUMBER_METHODS = new Set(["safe"]);
|
|
33635
|
+
const FUNCTION_FACTORY = new Set(["function"]);
|
|
33636
|
+
const FUNCTION_CHAIN_METHODS = new Set(["args", "returns"]);
|
|
33637
|
+
const DEPRECATED_TOP_LEVEL_FACTORIES = new Set([
|
|
33638
|
+
"nativeEnum",
|
|
33639
|
+
"ostring",
|
|
33640
|
+
"onumber",
|
|
33641
|
+
"oboolean",
|
|
33642
|
+
"oarray",
|
|
33643
|
+
"promise"
|
|
33644
|
+
]);
|
|
33645
|
+
const FACTORIES_WITH_DROPPED_CREATE = new Set([
|
|
33646
|
+
"any",
|
|
33647
|
+
"array",
|
|
33648
|
+
"bigint",
|
|
33649
|
+
"boolean",
|
|
33650
|
+
"date",
|
|
33651
|
+
"enum",
|
|
33652
|
+
"function",
|
|
33653
|
+
"literal",
|
|
33654
|
+
"map",
|
|
33655
|
+
"nativeEnum",
|
|
33656
|
+
"never",
|
|
33657
|
+
"null",
|
|
33658
|
+
"number",
|
|
33659
|
+
"object",
|
|
33660
|
+
"optional",
|
|
33661
|
+
"promise",
|
|
33662
|
+
"record",
|
|
33663
|
+
"set",
|
|
33664
|
+
"string",
|
|
33665
|
+
"tuple",
|
|
33666
|
+
"undefined",
|
|
33667
|
+
"union",
|
|
33668
|
+
"unknown",
|
|
33669
|
+
"void"
|
|
33670
|
+
]);
|
|
33671
|
+
const ENUM_PROPERTY_ALIASES = new Set(["Enum", "Values"]);
|
|
33672
|
+
const ENUM_FACTORY = new Set(["enum"]);
|
|
33673
|
+
const RECORD_FACTORY = new Set(["record"]);
|
|
33674
|
+
const LITERAL_FACTORY = new Set(["literal"]);
|
|
33675
|
+
const reportSchemaMigration = (context, node) => {
|
|
33676
|
+
context.report({
|
|
33677
|
+
node,
|
|
33678
|
+
message: "This Zod API is deprecated or changed in Zod 4; migrate to the recommended Zod 4 schema API."
|
|
33679
|
+
});
|
|
33680
|
+
};
|
|
33681
|
+
const isCallToDeprecatedTopLevelFactory = (callExpression) => isZodFactoryCall(callExpression, DEPRECATED_TOP_LEVEL_FACTORIES);
|
|
33682
|
+
const isCallToDroppedCreateFactory = (callExpression) => {
|
|
33683
|
+
const methodCall = getMethodCall(callExpression);
|
|
33684
|
+
if (!methodCall || methodCall.methodName !== "create") return false;
|
|
33685
|
+
const receiver = stripParenExpression(methodCall.receiver);
|
|
33686
|
+
const namespaceMemberName = getZodNamespaceMemberName(receiver);
|
|
33687
|
+
if (namespaceMemberName !== null) return FACTORIES_WITH_DROPPED_CREATE.has(namespaceMemberName);
|
|
33688
|
+
if (!isNodeOfType(receiver, "Identifier")) return false;
|
|
33689
|
+
const imported = getZodNamedImport(receiver);
|
|
33690
|
+
return imported !== null && FACTORIES_WITH_DROPPED_CREATE.has(imported);
|
|
33691
|
+
};
|
|
33692
|
+
const isSingleArgumentRecordCall = (callExpression) => callExpression.arguments.length === 1 && isZodFactoryCall(callExpression, RECORD_FACTORY);
|
|
33693
|
+
const isDeprecatedFunctionChainCall = (callExpression) => isDirectMethodCallOnZodFactory(callExpression, FUNCTION_FACTORY, FUNCTION_CHAIN_METHODS);
|
|
33694
|
+
const isSymbolLiteralArgument = (node) => {
|
|
33695
|
+
if (!node) return false;
|
|
33696
|
+
const inner = stripParenExpression(node);
|
|
33697
|
+
if (isNodeOfType(inner, "CallExpression")) {
|
|
33698
|
+
const callee = stripParenExpression(inner.callee);
|
|
33699
|
+
return isNodeOfType(callee, "Identifier") && callee.name === "Symbol";
|
|
33700
|
+
}
|
|
33701
|
+
if (!isNodeOfType(inner, "MemberExpression")) return false;
|
|
33702
|
+
const object = stripParenExpression(inner.object);
|
|
33703
|
+
return isNodeOfType(object, "Identifier") && object.name === "Symbol";
|
|
33704
|
+
};
|
|
33705
|
+
const isLiteralSymbolCall = (callExpression) => callExpression.arguments.length > 0 && isZodFactoryCall(callExpression, LITERAL_FACTORY) && isSymbolLiteralArgument(callExpression.arguments[0]);
|
|
33706
|
+
const isDroppedEnumAliasAccess = (memberExpression) => {
|
|
33707
|
+
const propertyName = getStaticPropertyName(memberExpression);
|
|
33708
|
+
if (propertyName === null || !ENUM_PROPERTY_ALIASES.has(propertyName)) return false;
|
|
33709
|
+
const receiver = stripParenExpression(memberExpression.object);
|
|
33710
|
+
return isNodeOfType(receiver, "CallExpression") && isZodFactoryCall(receiver, ENUM_FACTORY);
|
|
33711
|
+
};
|
|
33712
|
+
const isRefineSecondArgumentFunction = (callExpression) => {
|
|
33713
|
+
const methodCall = getMethodCall(callExpression);
|
|
33714
|
+
if (!methodCall || methodCall.methodName !== "refine") return false;
|
|
33715
|
+
const receiver = stripParenExpression(methodCall.receiver);
|
|
33716
|
+
if (!isNodeOfType(receiver, "CallExpression")) return false;
|
|
33717
|
+
if (!isZodFactoryCall(receiver, FACTORIES_WITH_DROPPED_CREATE)) return false;
|
|
33718
|
+
const secondArgument = callExpression.arguments[1];
|
|
33719
|
+
return isNodeOfType(secondArgument, "FunctionExpression") || isNodeOfType(secondArgument, "ArrowFunctionExpression");
|
|
33720
|
+
};
|
|
33721
|
+
const isZodNamespaceImportMemberCreate = (memberExpression) => {
|
|
33722
|
+
if (getStaticPropertyName(memberExpression) !== "create") return false;
|
|
33723
|
+
const receiver = stripParenExpression(memberExpression.object);
|
|
33724
|
+
if (!isNodeOfType(receiver, "MemberExpression")) return false;
|
|
33725
|
+
const factoryName = getStaticPropertyName(receiver);
|
|
33726
|
+
return factoryName !== null && FACTORIES_WITH_DROPPED_CREATE.has(factoryName) && isZodNamespaceIdentifier(receiver.object);
|
|
33727
|
+
};
|
|
33728
|
+
const zodV4NoDeprecatedSchemaApis = defineRule({
|
|
33729
|
+
id: "zod-v4-no-deprecated-schema-apis",
|
|
33730
|
+
requires: ["zod:4"],
|
|
33731
|
+
tags: ["migration-hint"],
|
|
33732
|
+
severity: "warn",
|
|
33733
|
+
recommendation: "Migrate Zod 4 schema APIs that were deprecated, changed, or removed: use top-level factories such as `z.enum()`, object helpers such as `z.strictObject()`, the new `z.function({ input, output })` form, and explicit key/value schemas for `z.record()`.",
|
|
33734
|
+
create: (context) => ({
|
|
33735
|
+
CallExpression(node) {
|
|
33736
|
+
if (isCallToDeprecatedTopLevelFactory(node) || isCallToDroppedCreateFactory(node) || isSingleArgumentRecordCall(node) || isLiteralSymbolCall(node) || isDeprecatedFunctionChainCall(node) || isDirectMethodCallOnZodFactory(node, OBJECT_FACTORY, OBJECT_METHODS) || isDirectMethodCallOnZodFactory(node, NUMBER_FACTORY, NUMBER_METHODS) || isRefineSecondArgumentFunction(node)) reportSchemaMigration(context, node);
|
|
33737
|
+
},
|
|
33738
|
+
MemberExpression(node) {
|
|
33739
|
+
const parent = node.parent;
|
|
33740
|
+
if (parent && isNodeOfType(parent, "CallExpression") && stripParenExpression(parent.callee) === node) return;
|
|
33741
|
+
if (isDroppedEnumAliasAccess(node) || isZodNamespaceImportMemberCreate(node)) reportSchemaMigration(context, node);
|
|
33742
|
+
}
|
|
33743
|
+
})
|
|
33744
|
+
});
|
|
33745
|
+
//#endregion
|
|
33746
|
+
//#region src/plugin/rules/zod/zod-v4-prefer-top-level-string-formats.ts
|
|
33747
|
+
const ZOD_STRING_FACTORY = new Set(["string"]);
|
|
33748
|
+
const STRING_FORMAT_METHODS = new Set([
|
|
33749
|
+
"base64",
|
|
33750
|
+
"base64url",
|
|
33751
|
+
"cidr",
|
|
33752
|
+
"cidrv4",
|
|
33753
|
+
"cidrv6",
|
|
33754
|
+
"cuid",
|
|
33755
|
+
"cuid2",
|
|
33756
|
+
"date",
|
|
33757
|
+
"datetime",
|
|
33758
|
+
"duration",
|
|
33759
|
+
"email",
|
|
33760
|
+
"emoji",
|
|
33761
|
+
"ip",
|
|
33762
|
+
"ipv4",
|
|
33763
|
+
"ipv6",
|
|
33764
|
+
"jwt",
|
|
33765
|
+
"nanoid",
|
|
33766
|
+
"time",
|
|
33767
|
+
"ulid",
|
|
33768
|
+
"url",
|
|
33769
|
+
"uuid"
|
|
33770
|
+
]);
|
|
33771
|
+
const zodV4PreferTopLevelStringFormats = defineRule({
|
|
33772
|
+
id: "zod-v4-prefer-top-level-string-formats",
|
|
33773
|
+
requires: ["zod:4"],
|
|
33774
|
+
tags: ["migration-hint"],
|
|
33775
|
+
severity: "warn",
|
|
33776
|
+
recommendation: "Replace deprecated `z.string().<format>()` calls with Zod 4 top-level string format APIs like `z.email()`, `z.uuid()`, `z.ipv4()`, or `z.cidrv4()`.",
|
|
33777
|
+
create: (context) => ({ CallExpression(node) {
|
|
33778
|
+
if (!isDirectMethodCallOnZodFactory(node, ZOD_STRING_FACTORY, STRING_FORMAT_METHODS)) return;
|
|
33779
|
+
context.report({
|
|
33780
|
+
node,
|
|
33781
|
+
message: "Zod 4 deprecates string format methods on `z.string()`; use the matching top-level Zod format API instead."
|
|
33782
|
+
});
|
|
33783
|
+
} })
|
|
33784
|
+
});
|
|
33785
|
+
//#endregion
|
|
33361
33786
|
//#region src/plugin/rule-registry.ts
|
|
33362
33787
|
const reactDoctorRules = [
|
|
33363
33788
|
{
|
|
@@ -36894,6 +37319,50 @@ const reactDoctorRules = [
|
|
|
36894
37319
|
framework: "global",
|
|
36895
37320
|
category: "Correctness"
|
|
36896
37321
|
}
|
|
37322
|
+
},
|
|
37323
|
+
{
|
|
37324
|
+
key: "react-doctor/zod-v4-no-deprecated-error-apis",
|
|
37325
|
+
id: "zod-v4-no-deprecated-error-apis",
|
|
37326
|
+
source: "react-doctor",
|
|
37327
|
+
originallyExternal: false,
|
|
37328
|
+
rule: {
|
|
37329
|
+
...zodV4NoDeprecatedErrorApis,
|
|
37330
|
+
framework: "global",
|
|
37331
|
+
category: "Architecture"
|
|
37332
|
+
}
|
|
37333
|
+
},
|
|
37334
|
+
{
|
|
37335
|
+
key: "react-doctor/zod-v4-no-deprecated-error-customization",
|
|
37336
|
+
id: "zod-v4-no-deprecated-error-customization",
|
|
37337
|
+
source: "react-doctor",
|
|
37338
|
+
originallyExternal: false,
|
|
37339
|
+
rule: {
|
|
37340
|
+
...zodV4NoDeprecatedErrorCustomization,
|
|
37341
|
+
framework: "global",
|
|
37342
|
+
category: "Architecture"
|
|
37343
|
+
}
|
|
37344
|
+
},
|
|
37345
|
+
{
|
|
37346
|
+
key: "react-doctor/zod-v4-no-deprecated-schema-apis",
|
|
37347
|
+
id: "zod-v4-no-deprecated-schema-apis",
|
|
37348
|
+
source: "react-doctor",
|
|
37349
|
+
originallyExternal: false,
|
|
37350
|
+
rule: {
|
|
37351
|
+
...zodV4NoDeprecatedSchemaApis,
|
|
37352
|
+
framework: "global",
|
|
37353
|
+
category: "Architecture"
|
|
37354
|
+
}
|
|
37355
|
+
},
|
|
37356
|
+
{
|
|
37357
|
+
key: "react-doctor/zod-v4-prefer-top-level-string-formats",
|
|
37358
|
+
id: "zod-v4-prefer-top-level-string-formats",
|
|
37359
|
+
source: "react-doctor",
|
|
37360
|
+
originallyExternal: false,
|
|
37361
|
+
rule: {
|
|
37362
|
+
...zodV4PreferTopLevelStringFormats,
|
|
37363
|
+
framework: "global",
|
|
37364
|
+
category: "Architecture"
|
|
37365
|
+
}
|
|
36897
37366
|
}
|
|
36898
37367
|
];
|
|
36899
37368
|
const ruleRegistry = Object.fromEntries(reactDoctorRules.map((rule) => [rule.id, rule.rule]));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "oxlint-plugin-react-doctor",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.12-dev.269ca17",
|
|
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",
|