oxlint-plugin-react-doctor 0.6.2-dev.da3b19c → 0.6.2-dev.fa61c20
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 +315 -401
- 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
|
|
@@ -788,6 +783,24 @@ const collectChildComponentNames = (element, into) => {
|
|
|
788
783
|
into.add(name);
|
|
789
784
|
});
|
|
790
785
|
};
|
|
786
|
+
const findSameFileComponentBody = (programRoot, componentName) => {
|
|
787
|
+
let foundBody = null;
|
|
788
|
+
walkAst(programRoot, (node) => {
|
|
789
|
+
if (foundBody) return false;
|
|
790
|
+
if (isNodeOfType(node, "FunctionDeclaration") && node.id && node.id.name === componentName) {
|
|
791
|
+
foundBody = node.body;
|
|
792
|
+
return false;
|
|
793
|
+
}
|
|
794
|
+
if (isNodeOfType(node, "VariableDeclarator") && isNodeOfType(node.id, "Identifier") && node.id.name === componentName) {
|
|
795
|
+
const initializer = node.init;
|
|
796
|
+
if (isNodeOfType(initializer, "ArrowFunctionExpression") || isNodeOfType(initializer, "FunctionExpression")) {
|
|
797
|
+
foundBody = initializer.body;
|
|
798
|
+
return false;
|
|
799
|
+
}
|
|
800
|
+
}
|
|
801
|
+
});
|
|
802
|
+
return foundBody;
|
|
803
|
+
};
|
|
791
804
|
const countEffectHookCalls = (body) => {
|
|
792
805
|
if (!body) return 0;
|
|
793
806
|
let count = 0;
|
|
@@ -797,36 +810,6 @@ const countEffectHookCalls = (body) => {
|
|
|
797
810
|
});
|
|
798
811
|
return count;
|
|
799
812
|
};
|
|
800
|
-
const componentEffectIndexCache = /* @__PURE__ */ new WeakMap();
|
|
801
|
-
const getComponentEffectIndex = (programRoot) => {
|
|
802
|
-
const cached = componentEffectIndexCache.get(programRoot);
|
|
803
|
-
if (cached) return cached;
|
|
804
|
-
const bodyByName = /* @__PURE__ */ new Map();
|
|
805
|
-
walkAst(programRoot, (node) => {
|
|
806
|
-
if (isNodeOfType(node, "FunctionDeclaration") && node.id && !bodyByName.has(node.id.name)) {
|
|
807
|
-
bodyByName.set(node.id.name, node.body);
|
|
808
|
-
return;
|
|
809
|
-
}
|
|
810
|
-
if (isNodeOfType(node, "VariableDeclarator") && isNodeOfType(node.id, "Identifier") && !bodyByName.has(node.id.name)) {
|
|
811
|
-
const initializer = node.init;
|
|
812
|
-
if (isNodeOfType(initializer, "ArrowFunctionExpression") || isNodeOfType(initializer, "FunctionExpression")) bodyByName.set(node.id.name, initializer.body);
|
|
813
|
-
}
|
|
814
|
-
});
|
|
815
|
-
const index = {
|
|
816
|
-
bodyByName,
|
|
817
|
-
effectCountByName: /* @__PURE__ */ new Map()
|
|
818
|
-
};
|
|
819
|
-
componentEffectIndexCache.set(programRoot, index);
|
|
820
|
-
return index;
|
|
821
|
-
};
|
|
822
|
-
const getSameFileComponentEffectCount = (programRoot, componentName) => {
|
|
823
|
-
const index = getComponentEffectIndex(programRoot);
|
|
824
|
-
const cachedCount = index.effectCountByName.get(componentName);
|
|
825
|
-
if (cachedCount !== void 0) return cachedCount;
|
|
826
|
-
const count = countEffectHookCalls(index.bodyByName.get(componentName) ?? null);
|
|
827
|
-
index.effectCountByName.set(componentName, count);
|
|
828
|
-
return count;
|
|
829
|
-
};
|
|
830
813
|
const activityWrapsEffectHeavySubtree = defineRule({
|
|
831
814
|
id: "activity-wraps-effect-heavy-subtree",
|
|
832
815
|
title: "Activity wraps an effect-heavy subtree",
|
|
@@ -883,7 +866,9 @@ const activityWrapsEffectHeavySubtree = defineRule({
|
|
|
883
866
|
let totalEffects = 0;
|
|
884
867
|
const effectfulChildren = [];
|
|
885
868
|
for (const componentName of childComponentNames) {
|
|
886
|
-
const
|
|
869
|
+
const body = findSameFileComponentBody(programRoot, componentName);
|
|
870
|
+
if (!body) continue;
|
|
871
|
+
const effectCount = countEffectHookCalls(body);
|
|
887
872
|
if (effectCount === 0) continue;
|
|
888
873
|
totalEffects += effectCount;
|
|
889
874
|
effectfulChildren.push(`<${componentName}>`);
|
|
@@ -1682,7 +1667,7 @@ const isNextjsMetadataImageRouteFilename = (rawFilename) => {
|
|
|
1682
1667
|
};
|
|
1683
1668
|
//#endregion
|
|
1684
1669
|
//#region src/plugin/utils/normalize-filename.ts
|
|
1685
|
-
const normalizeFilename = (filename) => filename.
|
|
1670
|
+
const normalizeFilename = (filename) => filename.replaceAll("\\", "/");
|
|
1686
1671
|
//#endregion
|
|
1687
1672
|
//#region src/plugin/utils/is-generated-image-render-context.ts
|
|
1688
1673
|
const IMAGE_RESPONSE_MODULES = ["next/og", "@vercel/og"];
|
|
@@ -3647,9 +3632,8 @@ const SECRET_FALSE_POSITIVE_SUFFIXES = new Set([
|
|
|
3647
3632
|
const escapeRegExp = (value) => value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
3648
3633
|
//#endregion
|
|
3649
3634
|
//#region src/plugin/rules/security-scan/utils/find-suspicious-public-env-secret-name.ts
|
|
3650
|
-
const PUBLIC_ENV_SECRET_NAME_GLOBAL_PATTERN = new RegExp(PUBLIC_ENV_SECRET_NAME_PATTERN.source, "gi");
|
|
3651
3635
|
const findSuspiciousPublicEnvSecretNamePattern = (content) => {
|
|
3652
|
-
for (const match of content.matchAll(
|
|
3636
|
+
for (const match of content.matchAll(new RegExp(PUBLIC_ENV_SECRET_NAME_PATTERN.source, "gi"))) {
|
|
3653
3637
|
const value = match[0] ?? "";
|
|
3654
3638
|
if (!TRUSTED_PUBLIC_SECRET_NAME_PATTERN.test(value)) return new RegExp(escapeRegExp(value));
|
|
3655
3639
|
}
|
|
@@ -4116,56 +4100,62 @@ const collectPatternIdentifiers = (pattern, target) => {
|
|
|
4116
4100
|
for (const element of pattern.elements ?? []) if (element) collectPatternIdentifiers(element, target);
|
|
4117
4101
|
} else if (isNodeOfType(pattern, "AssignmentPattern") && pattern.left) collectPatternIdentifiers(pattern.left, target);
|
|
4118
4102
|
};
|
|
4103
|
+
const collectAssignedIdentifiers = (block) => {
|
|
4104
|
+
const assigned = /* @__PURE__ */ new Set();
|
|
4105
|
+
walkAst(block, (child) => {
|
|
4106
|
+
if (isInlineFunctionExpression(child) || isNodeOfType(child, "FunctionDeclaration")) return false;
|
|
4107
|
+
if (isNodeOfType(child, "AssignmentExpression") && child.left) collectPatternIdentifiers(child.left, assigned);
|
|
4108
|
+
});
|
|
4109
|
+
return assigned;
|
|
4110
|
+
};
|
|
4111
|
+
const collectAwaitedArgIdentifiers = (block) => {
|
|
4112
|
+
const referenced = /* @__PURE__ */ new Set();
|
|
4113
|
+
walkAst(block, (child) => {
|
|
4114
|
+
if (isInlineFunctionExpression(child) || isNodeOfType(child, "FunctionDeclaration")) return false;
|
|
4115
|
+
if (!isNodeOfType(child, "AwaitExpression") || !child.argument) return;
|
|
4116
|
+
collectReferenceIdentifierNames(child.argument, referenced);
|
|
4117
|
+
});
|
|
4118
|
+
return referenced;
|
|
4119
|
+
};
|
|
4119
4120
|
const ARRAY_MUTATION_METHOD_NAMES = new Set([
|
|
4120
4121
|
"push",
|
|
4121
4122
|
"unshift",
|
|
4122
4123
|
"splice"
|
|
4123
4124
|
]);
|
|
4124
|
-
const
|
|
4125
|
-
const
|
|
4125
|
+
const collectMutatedArrayNames = (block) => {
|
|
4126
|
+
const mutated = /* @__PURE__ */ new Set();
|
|
4126
4127
|
walkAst(block, (child) => {
|
|
4127
4128
|
if (child !== block && isFunctionLike$1(child)) return false;
|
|
4128
|
-
if (!isNodeOfType(child, "
|
|
4129
|
-
|
|
4130
|
-
|
|
4131
|
-
collectReferenceIdentifierNames(child.init, referencedNames);
|
|
4132
|
-
declaratorBindings.push({
|
|
4133
|
-
declaredName: child.id.name,
|
|
4134
|
-
referencedNames
|
|
4135
|
-
});
|
|
4129
|
+
if (!isNodeOfType(child, "CallExpression")) return;
|
|
4130
|
+
const callee = child.callee;
|
|
4131
|
+
if (isNodeOfType(callee, "MemberExpression") && !callee.computed && isNodeOfType(callee.property, "Identifier") && ARRAY_MUTATION_METHOD_NAMES.has(callee.property.name) && isNodeOfType(callee.object, "Identifier")) mutated.add(callee.object.name);
|
|
4136
4132
|
});
|
|
4133
|
+
return mutated;
|
|
4134
|
+
};
|
|
4135
|
+
const addDerivedBindings = (block, names) => {
|
|
4137
4136
|
let didGrow = true;
|
|
4138
4137
|
while (didGrow) {
|
|
4139
4138
|
didGrow = false;
|
|
4140
|
-
|
|
4141
|
-
if (
|
|
4142
|
-
|
|
4143
|
-
|
|
4139
|
+
walkAst(block, (child) => {
|
|
4140
|
+
if (child !== block && isFunctionLike$1(child)) return false;
|
|
4141
|
+
if (!isNodeOfType(child, "VariableDeclarator") || !child.init) return;
|
|
4142
|
+
if (!isNodeOfType(child.id, "Identifier") || names.has(child.id.name)) return;
|
|
4143
|
+
const initReferences = /* @__PURE__ */ new Set();
|
|
4144
|
+
collectReferenceIdentifierNames(child.init, initReferences);
|
|
4145
|
+
for (const referenced of initReferences) if (names.has(referenced)) {
|
|
4146
|
+
names.add(child.id.name);
|
|
4144
4147
|
didGrow = true;
|
|
4145
4148
|
break;
|
|
4146
4149
|
}
|
|
4147
|
-
}
|
|
4150
|
+
});
|
|
4148
4151
|
}
|
|
4149
4152
|
};
|
|
4150
4153
|
const hasLoopCarriedDependency = (block) => {
|
|
4151
|
-
const carried =
|
|
4152
|
-
const
|
|
4153
|
-
walkAst(block, (child) => {
|
|
4154
|
-
if (child !== block && isFunctionLike$1(child)) return false;
|
|
4155
|
-
if (isNodeOfType(child, "AssignmentExpression") && child.left) {
|
|
4156
|
-
collectPatternIdentifiers(child.left, carried);
|
|
4157
|
-
return;
|
|
4158
|
-
}
|
|
4159
|
-
if (isNodeOfType(child, "AwaitExpression") && child.argument) {
|
|
4160
|
-
collectReferenceIdentifierNames(child.argument, awaitedReferences);
|
|
4161
|
-
return;
|
|
4162
|
-
}
|
|
4163
|
-
if (!isNodeOfType(child, "CallExpression")) return;
|
|
4164
|
-
const callee = child.callee;
|
|
4165
|
-
if (isNodeOfType(callee, "MemberExpression") && !callee.computed && isNodeOfType(callee.property, "Identifier") && ARRAY_MUTATION_METHOD_NAMES.has(callee.property.name) && isNodeOfType(callee.object, "Identifier")) carried.add(callee.object.name);
|
|
4166
|
-
});
|
|
4154
|
+
const carried = collectAssignedIdentifiers(block);
|
|
4155
|
+
for (const name of collectMutatedArrayNames(block)) carried.add(name);
|
|
4167
4156
|
if (carried.size === 0) return false;
|
|
4168
4157
|
addDerivedBindings(block, carried);
|
|
4158
|
+
const awaitedReferences = collectAwaitedArgIdentifiers(block);
|
|
4169
4159
|
for (const name of carried) if (awaitedReferences.has(name)) return true;
|
|
4170
4160
|
return false;
|
|
4171
4161
|
};
|
|
@@ -4843,8 +4833,7 @@ const FORM_CONTROL_TAGS = new Set([
|
|
|
4843
4833
|
]);
|
|
4844
4834
|
const resolveSettings$48 = (settings) => {
|
|
4845
4835
|
const reactDoctor = settings?.["react-doctor"];
|
|
4846
|
-
|
|
4847
|
-
return { inputComponents: new Set(ruleSettings.inputComponents ?? []) };
|
|
4836
|
+
return { inputComponents: (typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.autocompleteValid ?? {} : {}).inputComponents ?? [] };
|
|
4848
4837
|
};
|
|
4849
4838
|
const autocompleteValid = defineRule({
|
|
4850
4839
|
id: "autocomplete-valid",
|
|
@@ -4857,7 +4846,7 @@ const autocompleteValid = defineRule({
|
|
|
4857
4846
|
const settings = resolveSettings$48(context.settings);
|
|
4858
4847
|
return { JSXOpeningElement: (node) => {
|
|
4859
4848
|
const tag = getElementType(node, context.settings);
|
|
4860
|
-
if (!FORM_CONTROL_TAGS.has(tag) && !settings.inputComponents.
|
|
4849
|
+
if (!FORM_CONTROL_TAGS.has(tag) && !settings.inputComponents.includes(tag)) return;
|
|
4861
4850
|
const attribute = hasJsxPropIgnoreCase(node.attributes, "autoComplete");
|
|
4862
4851
|
if (!attribute) return;
|
|
4863
4852
|
const value = getJsxPropStringValue(attribute);
|
|
@@ -5719,20 +5708,12 @@ const getTemplateInterpolations = (valueTail) => {
|
|
|
5719
5708
|
return interpolations === null ? "" : interpolations.join(" ");
|
|
5720
5709
|
};
|
|
5721
5710
|
const isInertParseTarget = (target, fileContent) => {
|
|
5722
|
-
const fileHasCreateElement = fileContent.includes("createElement");
|
|
5723
|
-
const fileHasIsolatedDocument = fileContent.includes("createHTMLDocument");
|
|
5724
|
-
if (!fileHasCreateElement && !fileHasIsolatedDocument) return false;
|
|
5725
5711
|
const escapedTarget = escapeRegExp(target);
|
|
5726
5712
|
const escapedRoot = escapeRegExp(target.split(".")[0] ?? target);
|
|
5727
5713
|
if (new RegExp(`\\b${escapedRoot}\\s*=\\s*[^\\n;]*(?:getElementById|querySelector|getElementsBy|\\.current\\b|document\\.(?:body|head|documentElement))`).test(fileContent)) return false;
|
|
5728
|
-
if (
|
|
5729
|
-
|
|
5730
|
-
|
|
5731
|
-
}
|
|
5732
|
-
if (fileHasIsolatedDocument) {
|
|
5733
|
-
if (new RegExp(`${escapedRoot}\\s*=\\s*[^\\n;]*\\bcreateHTMLDocument\\s*\\(`).test(fileContent)) return true;
|
|
5734
|
-
}
|
|
5735
|
-
if (!fileHasCreateElement) return false;
|
|
5714
|
+
if (new RegExp(`${escapedTarget}\\s*=\\s*document\\.createElement\\(\\s*["'\`]template["'\`]`).test(fileContent)) return true;
|
|
5715
|
+
if (new RegExp(`${escapedRoot}\\s*=\\s*[^\\n;]*\\bcreateElement\\(\\s*["'\`](?:style|textarea)["'\`]`).test(fileContent)) return true;
|
|
5716
|
+
if (new RegExp(`${escapedRoot}\\s*=\\s*[^\\n;]*\\bcreateHTMLDocument\\s*\\(`).test(fileContent)) return true;
|
|
5736
5717
|
if (!new RegExp(`${escapedRoot}\\s*=\\s*[^\\n;]*\\bcreateElement\\s*\\(`).test(fileContent)) return false;
|
|
5737
5718
|
const attachedToLiveTreePattern = new RegExp(`${LIVE_DOM_ATTACH_PATTERN.source}[^)]*\\b${escapedRoot}\\b`);
|
|
5738
5719
|
const returnedAsNodePattern = new RegExp(`\\breturn\\b[^\\n]*\\b${escapedRoot}\\b(?!\\s*\\.\\s*(?:textContent|innerText|innerHTML|outerHTML))`);
|
|
@@ -5825,9 +5806,10 @@ const dangerousHtmlSink = defineRule({
|
|
|
5825
5806
|
const valueIdentifier = valueExpression.match(/^[\w$]+/)?.[0];
|
|
5826
5807
|
if (valueIdentifier !== void 0) {
|
|
5827
5808
|
const escapedIdentifier = escapeRegExp(valueIdentifier);
|
|
5828
|
-
|
|
5829
|
-
|
|
5830
|
-
|
|
5809
|
+
const fromSerializer = new RegExp(`\\b${escapedIdentifier}\\b\\s*${SERIALIZER_ASSIGNMENT_PATTERN.source}`, "i");
|
|
5810
|
+
const fromSanitizer = new RegExp(`\\b${escapedIdentifier}\\b\\s*${SANITIZED_ASSIGNMENT_PATTERN.source}`, "i");
|
|
5811
|
+
const fromDomContent = new RegExp(`\\b${escapedIdentifier}\\b\\s*${DOM_CONTENT_ASSIGNMENT_PATTERN.source}`);
|
|
5812
|
+
if (fromSerializer.test(file.content) || fromSanitizer.test(file.content) || fromDomContent.test(file.content)) continue;
|
|
5831
5813
|
}
|
|
5832
5814
|
}
|
|
5833
5815
|
const sinkTargetMatch = INNERHTML_TARGET_PATTERN.exec(line);
|
|
@@ -6218,10 +6200,7 @@ const isReactVersionAtLeast$1 = (version, major, minor) => {
|
|
|
6218
6200
|
const actualMinor = Number(match[2]);
|
|
6219
6201
|
return actualMajor > major || actualMajor === major && actualMinor >= minor;
|
|
6220
6202
|
};
|
|
6221
|
-
const containsJsxCache = /* @__PURE__ */ new WeakMap();
|
|
6222
6203
|
const containsJsx$1 = (root) => {
|
|
6223
|
-
const cached = containsJsxCache.get(root);
|
|
6224
|
-
if (cached !== void 0) return cached;
|
|
6225
6204
|
let found = false;
|
|
6226
6205
|
const visit = (node) => {
|
|
6227
6206
|
if (found) return;
|
|
@@ -6244,7 +6223,6 @@ const containsJsx$1 = (root) => {
|
|
|
6244
6223
|
}
|
|
6245
6224
|
};
|
|
6246
6225
|
visit(root);
|
|
6247
|
-
containsJsxCache.set(root, found);
|
|
6248
6226
|
return found;
|
|
6249
6227
|
};
|
|
6250
6228
|
const getStaticMemberName = (node) => {
|
|
@@ -6336,6 +6314,27 @@ const hasDisplayNameMember = (classNode) => {
|
|
|
6336
6314
|
for (const member of members) if ((isNodeOfType(member, "PropertyDefinition") || isNodeOfType(member, "MethodDefinition")) && "static" in member && member.static && isNodeOfType(member.key, "Identifier") && member.key.name === "displayName") return true;
|
|
6337
6315
|
return false;
|
|
6338
6316
|
};
|
|
6317
|
+
const hasDisplayNameAssignment = (className, programRoot) => {
|
|
6318
|
+
let found = false;
|
|
6319
|
+
const visit = (node) => {
|
|
6320
|
+
if (found) return;
|
|
6321
|
+
if (isNodeOfType(node, "AssignmentExpression") && isNodeOfType(node.left, "MemberExpression") && isNodeOfType(node.left.object, "Identifier") && node.left.object.name === className && getStaticMemberName(node.left) === "displayName") {
|
|
6322
|
+
found = true;
|
|
6323
|
+
return;
|
|
6324
|
+
}
|
|
6325
|
+
const record = node;
|
|
6326
|
+
for (const key of Object.keys(record)) {
|
|
6327
|
+
if (key === "parent") continue;
|
|
6328
|
+
const child = record[key];
|
|
6329
|
+
if (Array.isArray(child)) {
|
|
6330
|
+
for (const item of child) if (isAstNode(item)) visit(item);
|
|
6331
|
+
} else if (isAstNode(child)) visit(child);
|
|
6332
|
+
if (found) return;
|
|
6333
|
+
}
|
|
6334
|
+
};
|
|
6335
|
+
visit(programRoot);
|
|
6336
|
+
return found;
|
|
6337
|
+
};
|
|
6339
6338
|
const memberExpressionPath = (node) => {
|
|
6340
6339
|
if (isNodeOfType(node, "Identifier")) return [node.name];
|
|
6341
6340
|
if (!isNodeOfType(node, "MemberExpression")) return [];
|
|
@@ -6343,16 +6342,15 @@ const memberExpressionPath = (node) => {
|
|
|
6343
6342
|
const propertyName = getStaticMemberName(node);
|
|
6344
6343
|
return propertyName ? [...objectPath, propertyName] : objectPath;
|
|
6345
6344
|
};
|
|
6346
|
-
const
|
|
6347
|
-
|
|
6348
|
-
const cached = displayNameAssignmentIndexCache.get(programRoot);
|
|
6349
|
-
if (cached) return cached;
|
|
6350
|
-
const identifierTargets = /* @__PURE__ */ new Set();
|
|
6351
|
-
const memberPathSegments = /* @__PURE__ */ new Set();
|
|
6345
|
+
const hasDisplayNameAssignmentForProperty = (propertyName, programRoot) => {
|
|
6346
|
+
let found = false;
|
|
6352
6347
|
const visit = (node) => {
|
|
6348
|
+
if (found) return;
|
|
6353
6349
|
if (isNodeOfType(node, "AssignmentExpression") && isNodeOfType(node.left, "MemberExpression") && getStaticMemberName(node.left) === "displayName") {
|
|
6354
|
-
if (
|
|
6355
|
-
|
|
6350
|
+
if (memberExpressionPath(node.left.object).includes(propertyName)) {
|
|
6351
|
+
found = true;
|
|
6352
|
+
return;
|
|
6353
|
+
}
|
|
6356
6354
|
}
|
|
6357
6355
|
const record = node;
|
|
6358
6356
|
for (const key of Object.keys(record)) {
|
|
@@ -6361,18 +6359,12 @@ const getDisplayNameAssignmentIndex = (programRoot) => {
|
|
|
6361
6359
|
if (Array.isArray(child)) {
|
|
6362
6360
|
for (const item of child) if (isAstNode(item)) visit(item);
|
|
6363
6361
|
} else if (isAstNode(child)) visit(child);
|
|
6362
|
+
if (found) return;
|
|
6364
6363
|
}
|
|
6365
6364
|
};
|
|
6366
6365
|
visit(programRoot);
|
|
6367
|
-
|
|
6368
|
-
identifierTargets,
|
|
6369
|
-
memberPathSegments
|
|
6370
|
-
};
|
|
6371
|
-
displayNameAssignmentIndexCache.set(programRoot, index);
|
|
6372
|
-
return index;
|
|
6366
|
+
return found;
|
|
6373
6367
|
};
|
|
6374
|
-
const hasDisplayNameAssignment = (className, programRoot) => getDisplayNameAssignmentIndex(programRoot).identifierTargets.has(className);
|
|
6375
|
-
const hasDisplayNameAssignmentForProperty = (propertyName, programRoot) => getDisplayNameAssignmentIndex(programRoot).memberPathSegments.has(propertyName);
|
|
6376
6368
|
const displayName = defineRule({
|
|
6377
6369
|
id: "display-name",
|
|
6378
6370
|
title: "Component missing display name",
|
|
@@ -8637,7 +8629,7 @@ const DEFAULT_HEADING_TAGS = [
|
|
|
8637
8629
|
const resolveSettings$40 = (settings) => {
|
|
8638
8630
|
const reactDoctor = settings?.["react-doctor"];
|
|
8639
8631
|
const ruleSettings = typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.headingHasContent ?? {} : {};
|
|
8640
|
-
return { headingTags:
|
|
8632
|
+
return { headingTags: [...DEFAULT_HEADING_TAGS, ...ruleSettings.components ?? []] };
|
|
8641
8633
|
};
|
|
8642
8634
|
const headingHasContent = defineRule({
|
|
8643
8635
|
id: "heading-has-content",
|
|
@@ -8650,7 +8642,7 @@ const headingHasContent = defineRule({
|
|
|
8650
8642
|
const settings = resolveSettings$40(context.settings);
|
|
8651
8643
|
return { JSXOpeningElement(node) {
|
|
8652
8644
|
const elementType = getElementType(node, context.settings);
|
|
8653
|
-
if (!settings.headingTags.
|
|
8645
|
+
if (!settings.headingTags.includes(elementType)) return;
|
|
8654
8646
|
const parent = node.parent;
|
|
8655
8647
|
if (parent && isNodeOfType(parent, "JSXElement")) {
|
|
8656
8648
|
if (objectHasAccessibleChild(parent, context.settings)) return;
|
|
@@ -9556,7 +9548,6 @@ const KEYBOARD_EVENT_HANDLERS = [
|
|
|
9556
9548
|
"onKeyUp"
|
|
9557
9549
|
];
|
|
9558
9550
|
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
9551
|
//#endregion
|
|
9561
9552
|
//#region src/plugin/utils/is-disabled-element.ts
|
|
9562
9553
|
const isDisabledElement = (openingElement) => {
|
|
@@ -9616,16 +9607,7 @@ const interactiveSupportsFocus = defineRule({
|
|
|
9616
9607
|
const roleAttribute = hasJsxPropIgnoreCase(node.attributes, "role");
|
|
9617
9608
|
const role = roleAttribute ? getJsxPropStringValue(roleAttribute) : null;
|
|
9618
9609
|
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;
|
|
9610
|
+
if (!ALL_EVENT_HANDLERS.some((handler) => Boolean(hasJsxPropIgnoreCase(node.attributes, handler)))) return;
|
|
9629
9611
|
const elementType = getElementType(node, context.settings);
|
|
9630
9612
|
if (!HTML_TAGS.has(elementType)) return;
|
|
9631
9613
|
if (isDisabledElement(node) || isHiddenFromScreenReader(node, context.settings) || isPresentationRole(node)) return;
|
|
@@ -10268,6 +10250,8 @@ const jsCachePropertyAccess = defineRule({
|
|
|
10268
10250
|
walkAst(loopBody, (child) => {
|
|
10269
10251
|
if (isNodeOfType(child, "AssignmentExpression")) recordWriteTarget(child.left);
|
|
10270
10252
|
if (isNodeOfType(child, "UpdateExpression")) recordWriteTarget(child.argument);
|
|
10253
|
+
});
|
|
10254
|
+
walkAst(loopBody, (child) => {
|
|
10271
10255
|
if (!isNodeOfType(child, "MemberExpression")) return;
|
|
10272
10256
|
if (child.computed) return;
|
|
10273
10257
|
if (isNodeOfType(child.parent, "MemberExpression") && child.parent.object === child) return;
|
|
@@ -10818,11 +10802,7 @@ const isSingleFieldEqualityPredicate = (node) => {
|
|
|
10818
10802
|
if (!isNodeOfType(predicate, "BinaryExpression") || predicate.operator !== "===" && predicate.operator !== "==") return false;
|
|
10819
10803
|
return referencesParameter(predicate.left, firstParameter.name) || referencesParameter(predicate.right, firstParameter.name);
|
|
10820
10804
|
};
|
|
10821
|
-
const
|
|
10822
|
-
const getLoopBoundNames = (loop) => {
|
|
10823
|
-
const cached = loopBoundNamesCache.get(loop);
|
|
10824
|
-
if (cached) return cached;
|
|
10825
|
-
const names = /* @__PURE__ */ new Set();
|
|
10805
|
+
const collectLoopBoundNames = (loop, names) => {
|
|
10826
10806
|
if ((isNodeOfType(loop, "ForOfStatement") || isNodeOfType(loop, "ForInStatement")) && loop.left) walkAst(loop.left, (child) => {
|
|
10827
10807
|
if (isNodeOfType(child, "Identifier")) names.add(child.name);
|
|
10828
10808
|
});
|
|
@@ -10839,8 +10819,6 @@ const getLoopBoundNames = (loop) => {
|
|
|
10839
10819
|
if (targetRoot) names.add(targetRoot);
|
|
10840
10820
|
}
|
|
10841
10821
|
});
|
|
10842
|
-
loopBoundNamesCache.set(loop, names);
|
|
10843
|
-
return names;
|
|
10844
10822
|
};
|
|
10845
10823
|
const hasLoopBoundComputedIndex = (receiver, loopBoundNames) => {
|
|
10846
10824
|
let cursor = receiver;
|
|
@@ -10866,7 +10844,7 @@ const isLoopVariantReceiver = (node) => {
|
|
|
10866
10844
|
const loopBoundNames = /* @__PURE__ */ new Set();
|
|
10867
10845
|
let ancestor = node.parent;
|
|
10868
10846
|
while (ancestor) {
|
|
10869
|
-
if (LOOP_TYPES.includes(ancestor.type))
|
|
10847
|
+
if (LOOP_TYPES.includes(ancestor.type)) collectLoopBoundNames(ancestor, loopBoundNames);
|
|
10870
10848
|
ancestor = ancestor.parent;
|
|
10871
10849
|
}
|
|
10872
10850
|
if (loopBoundNames.has(receiverRoot)) return true;
|
|
@@ -14474,14 +14452,14 @@ const keyLifecycleRisk = defineRule({
|
|
|
14474
14452
|
//#region src/plugin/rules/a11y/label-has-associated-control.ts
|
|
14475
14453
|
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
14454
|
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 =
|
|
14455
|
+
const DEFAULT_CONTROL_COMPONENTS = [
|
|
14478
14456
|
"input",
|
|
14479
14457
|
"meter",
|
|
14480
14458
|
"output",
|
|
14481
14459
|
"progress",
|
|
14482
14460
|
"select",
|
|
14483
14461
|
"textarea"
|
|
14484
|
-
]
|
|
14462
|
+
];
|
|
14485
14463
|
const DEFAULT_LABEL_ATTRIBUTES = [
|
|
14486
14464
|
"alt",
|
|
14487
14465
|
"aria-label",
|
|
@@ -14493,8 +14471,8 @@ const resolveSettings$24 = (settings) => {
|
|
|
14493
14471
|
const jsxA11y = settings?.["jsx-a11y"];
|
|
14494
14472
|
const forAttributes = (typeof jsxA11y === "object" && jsxA11y !== null ? jsxA11y : {}).attributes?.for ?? ["htmlFor"];
|
|
14495
14473
|
return {
|
|
14496
|
-
labelComponents:
|
|
14497
|
-
labelAttributes: new Set([...DEFAULT_LABEL_ATTRIBUTES, ...ruleSettings.labelAttributes ?? []]),
|
|
14474
|
+
labelComponents: ["label", ...ruleSettings.labelComponents ?? []].sort(),
|
|
14475
|
+
labelAttributes: [...new Set([...DEFAULT_LABEL_ATTRIBUTES, ...ruleSettings.labelAttributes ?? []])].sort(),
|
|
14498
14476
|
controlComponents: ruleSettings.controlComponents ?? [],
|
|
14499
14477
|
assert: ruleSettings.assert ?? "either",
|
|
14500
14478
|
depth: Math.min(ruleSettings.depth ?? 5, 25),
|
|
@@ -14502,7 +14480,7 @@ const resolveSettings$24 = (settings) => {
|
|
|
14502
14480
|
};
|
|
14503
14481
|
};
|
|
14504
14482
|
const isControlComponent = (tagName, controlComponents) => {
|
|
14505
|
-
if (DEFAULT_CONTROL_COMPONENTS.
|
|
14483
|
+
if (DEFAULT_CONTROL_COMPONENTS.includes(tagName)) return true;
|
|
14506
14484
|
return controlComponents.some((pattern) => compileGlob(pattern).test(tagName));
|
|
14507
14485
|
};
|
|
14508
14486
|
const searchForNestedControl = (child, currentDepth, searchContext) => {
|
|
@@ -14528,7 +14506,7 @@ const searchForAccessibleLabel = (child, currentDepth, searchContext) => {
|
|
|
14528
14506
|
const attributeName = attribute.name;
|
|
14529
14507
|
if (!isNodeOfType(attributeName, "JSXIdentifier")) continue;
|
|
14530
14508
|
const propName = getJsxAttributeName(attributeName);
|
|
14531
|
-
if (!propName || !searchContext.labelAttributes.
|
|
14509
|
+
if (!propName || !searchContext.labelAttributes.includes(propName)) continue;
|
|
14532
14510
|
const attributeValue = attribute.value;
|
|
14533
14511
|
if (!attributeValue) continue;
|
|
14534
14512
|
if (isNodeOfType(attributeValue, "Literal") && typeof attributeValue.value === "string") {
|
|
@@ -14550,7 +14528,7 @@ const hasAccessibleLabel = (element, searchContext) => {
|
|
|
14550
14528
|
const attributeName = attribute.name;
|
|
14551
14529
|
if (!isNodeOfType(attributeName, "JSXIdentifier")) continue;
|
|
14552
14530
|
const propName = getJsxAttributeName(attributeName);
|
|
14553
|
-
if (propName && searchContext.labelAttributes.
|
|
14531
|
+
if (propName && searchContext.labelAttributes.includes(propName)) return true;
|
|
14554
14532
|
}
|
|
14555
14533
|
for (const child of element.children) if (searchForAccessibleLabel(child, 1, searchContext)) return true;
|
|
14556
14534
|
return false;
|
|
@@ -14573,7 +14551,7 @@ const labelHasAssociatedControl = defineRule({
|
|
|
14573
14551
|
if (isTestlikeFile) return;
|
|
14574
14552
|
const opening = node.openingElement;
|
|
14575
14553
|
const tagName = getElementType(opening, context.settings);
|
|
14576
|
-
if (!settings.labelComponents.
|
|
14554
|
+
if (!settings.labelComponents.includes(tagName)) return;
|
|
14577
14555
|
const hasHtmlFor = settings.forAttributes.some((attributeName) => Boolean(hasJsxPropIgnoreCase(opening.attributes, attributeName)));
|
|
14578
14556
|
const searchContext = {
|
|
14579
14557
|
depth: settings.depth,
|
|
@@ -15162,9 +15140,9 @@ const resolveSettings$23 = (settings) => {
|
|
|
15162
15140
|
const reactDoctor = settings?.["react-doctor"];
|
|
15163
15141
|
const ruleSettings = typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.mediaHasCaption ?? {} : {};
|
|
15164
15142
|
return {
|
|
15165
|
-
audio:
|
|
15166
|
-
video:
|
|
15167
|
-
track:
|
|
15143
|
+
audio: [...DEFAULT_AUDIO, ...ruleSettings.audio ?? []],
|
|
15144
|
+
video: [...DEFAULT_VIDEO, ...ruleSettings.video ?? []],
|
|
15145
|
+
track: [...DEFAULT_TRACK, ...ruleSettings.track ?? []]
|
|
15168
15146
|
};
|
|
15169
15147
|
};
|
|
15170
15148
|
const evaluateMuted = (attribute) => {
|
|
@@ -15193,7 +15171,7 @@ const childMayRenderTrack = (child, trackTags, settings) => {
|
|
|
15193
15171
|
let rendersCaptionTrack = false;
|
|
15194
15172
|
walkAst(expression, (inner) => {
|
|
15195
15173
|
if (rendersCaptionTrack) return false;
|
|
15196
|
-
if (isNodeOfType(inner, "JSXElement") && trackTags.
|
|
15174
|
+
if (isNodeOfType(inner, "JSXElement") && trackTags.includes(getElementType(inner.openingElement, settings)) && trackKindMightBeCaptions(inner.openingElement)) {
|
|
15197
15175
|
rendersCaptionTrack = true;
|
|
15198
15176
|
return false;
|
|
15199
15177
|
}
|
|
@@ -15211,7 +15189,7 @@ const mediaHasCaption = defineRule({
|
|
|
15211
15189
|
const settings = resolveSettings$23(context.settings);
|
|
15212
15190
|
return { JSXOpeningElement(node) {
|
|
15213
15191
|
const tag = getElementType(node, context.settings);
|
|
15214
|
-
if (!(settings.audio.
|
|
15192
|
+
if (!(settings.audio.includes(tag) || settings.video.includes(tag))) return;
|
|
15215
15193
|
if (evaluateMuted(hasJsxPropIgnoreCase(node.attributes, "muted")) === true) return;
|
|
15216
15194
|
const parent = node.parent;
|
|
15217
15195
|
if (!parent || !isNodeOfType(parent, "JSXElement")) {
|
|
@@ -15226,7 +15204,7 @@ const mediaHasCaption = defineRule({
|
|
|
15226
15204
|
if (!isNodeOfType(child, "JSXElement")) return false;
|
|
15227
15205
|
const opening = child.openingElement;
|
|
15228
15206
|
const childTag = getElementType(opening, context.settings);
|
|
15229
|
-
if (!settings.track.
|
|
15207
|
+
if (!settings.track.includes(childTag)) return false;
|
|
15230
15208
|
const kindAttribute = hasJsxPropIgnoreCase(opening.attributes, "kind");
|
|
15231
15209
|
if (!kindAttribute) return false;
|
|
15232
15210
|
let kindValue = kindAttribute.value;
|
|
@@ -15537,30 +15515,16 @@ const stripJsComments = (sourceText) => sourceText.replace(BLOCK_COMMENT_PATTERN
|
|
|
15537
15515
|
const DEFAULT_EXPORT_DECLARATION_PATTERN = /^\s*export\s+default\b/m;
|
|
15538
15516
|
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
15517
|
const LOCAL_EXPORT_SPECIFIER_DECLARATION_PATTERN$1 = /^\s*export\s+(?:type\s+)?\{([\s\S]*?)\}(?:\s+from\s+["'][^"']+["'])?\s*;?\s*(?:(?:\/\/[^\n]*)?\s*)/gm;
|
|
15540
|
-
const
|
|
15518
|
+
const doesSourceTextExportName = (sourceText, exportedName) => {
|
|
15541
15519
|
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;
|
|
15520
|
+
if (exportedName === "default" && DEFAULT_EXPORT_DECLARATION_PATTERN.test(strippedSource)) return true;
|
|
15521
|
+
for (const match of strippedSource.matchAll(NAMED_EXPORT_DECLARATION_PATTERN)) if (match[1] === exportedName) return true;
|
|
15522
|
+
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;
|
|
15523
|
+
return false;
|
|
15550
15524
|
};
|
|
15551
|
-
const exportNamesCache = /* @__PURE__ */ new Map();
|
|
15552
15525
|
const doesModuleExportName = (filePath, exportedName) => {
|
|
15553
15526
|
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);
|
|
15527
|
+
return doesSourceTextExportName(fs.readFileSync(filePath, "utf8"), exportedName);
|
|
15564
15528
|
} catch {
|
|
15565
15529
|
return false;
|
|
15566
15530
|
}
|
|
@@ -17443,15 +17407,8 @@ const getProgramAnalysis = (anyNode) => {
|
|
|
17443
17407
|
programToAnalysis.set(programNode, analysis);
|
|
17444
17408
|
return analysis;
|
|
17445
17409
|
};
|
|
17446
|
-
const scopeByNodeCache = /* @__PURE__ */ new WeakMap();
|
|
17447
17410
|
const getScopeForNode = (node, manager) => {
|
|
17448
17411
|
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
17412
|
let bestScope = null;
|
|
17456
17413
|
let bestSize = Infinity;
|
|
17457
17414
|
for (const scope of manager.scopes) {
|
|
@@ -17464,7 +17421,6 @@ const getScopeForNode = (node, manager) => {
|
|
|
17464
17421
|
bestScope = scope;
|
|
17465
17422
|
}
|
|
17466
17423
|
}
|
|
17467
|
-
scopeByNode.set(node, bestScope);
|
|
17468
17424
|
return bestScope;
|
|
17469
17425
|
};
|
|
17470
17426
|
//#endregion
|
|
@@ -17499,20 +17455,11 @@ const descend = (node, visit, visited = /* @__PURE__ */ new Set()) => {
|
|
|
17499
17455
|
} else if (isAstNode(child)) descend(child, visit, visited);
|
|
17500
17456
|
}
|
|
17501
17457
|
};
|
|
17502
|
-
const upstreamRefsCache = /* @__PURE__ */ new WeakMap();
|
|
17503
17458
|
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
17459
|
const refs = [];
|
|
17512
17460
|
ascend(analysis, ref, (upRef) => {
|
|
17513
17461
|
refs.push(upRef);
|
|
17514
17462
|
});
|
|
17515
|
-
upstreamByRef.set(ref, refs);
|
|
17516
17463
|
return refs;
|
|
17517
17464
|
};
|
|
17518
17465
|
const findDownstreamNodes = (topNode, type) => {
|
|
@@ -17522,24 +17469,11 @@ const findDownstreamNodes = (topNode, type) => {
|
|
|
17522
17469
|
});
|
|
17523
17470
|
return nodes;
|
|
17524
17471
|
};
|
|
17525
|
-
const refByIdentifierCache = /* @__PURE__ */ new WeakMap();
|
|
17526
17472
|
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
17473
|
const scope = getScopeForNode(identifier, analysis.scopeManager);
|
|
17535
|
-
if (scope)
|
|
17536
|
-
|
|
17537
|
-
|
|
17538
|
-
break;
|
|
17539
|
-
}
|
|
17540
|
-
}
|
|
17541
|
-
refByIdentifier.set(identifier, resolvedReference);
|
|
17542
|
-
return resolvedReference;
|
|
17474
|
+
if (!scope) return null;
|
|
17475
|
+
for (const reference of scope.references) if (reference.identifier === identifier) return reference;
|
|
17476
|
+
return null;
|
|
17543
17477
|
};
|
|
17544
17478
|
const downstreamRefsCache = /* @__PURE__ */ new WeakMap();
|
|
17545
17479
|
const getDownstreamRefs = (analysis, node) => {
|
|
@@ -17614,6 +17548,22 @@ const isEventualCallTo = (analysis, ref, predicate) => {
|
|
|
17614
17548
|
};
|
|
17615
17549
|
//#endregion
|
|
17616
17550
|
//#region src/plugin/rules/state-and-effects/utils/effect/react.ts
|
|
17551
|
+
const getOuterScopeContaining = (analysis, node) => {
|
|
17552
|
+
if (!node.range) return null;
|
|
17553
|
+
let best = null;
|
|
17554
|
+
let bestSize = Infinity;
|
|
17555
|
+
for (const scope of analysis.scopeManager.scopes) {
|
|
17556
|
+
const block = scope.block;
|
|
17557
|
+
if (!block?.range) continue;
|
|
17558
|
+
if (node.range[0] < block.range[0] || node.range[1] > block.range[1]) continue;
|
|
17559
|
+
const size = block.range[1] - block.range[0];
|
|
17560
|
+
if (size <= bestSize) {
|
|
17561
|
+
bestSize = size;
|
|
17562
|
+
best = scope;
|
|
17563
|
+
}
|
|
17564
|
+
}
|
|
17565
|
+
return best;
|
|
17566
|
+
};
|
|
17617
17567
|
const KNOWN_PURE_HOC_NAMES = new Set(["memo", "forwardRef"]);
|
|
17618
17568
|
const startsWithUppercase = (name) => Boolean(name && name.length > 0 && name[0] >= "A" && name[0] <= "Z");
|
|
17619
17569
|
const isReactFunctionalComponent = (node) => {
|
|
@@ -17644,7 +17594,7 @@ const isReactFunctionalHOC = (analysis, node) => {
|
|
|
17644
17594
|
const isWrappedSeparately = () => {
|
|
17645
17595
|
if (!isNodeOfType(node.id, "Identifier")) return false;
|
|
17646
17596
|
const bindingName = node.id.name;
|
|
17647
|
-
const containingScope =
|
|
17597
|
+
const containingScope = getOuterScopeContaining(analysis, node);
|
|
17648
17598
|
if (!containingScope) return false;
|
|
17649
17599
|
const variable = containingScope.variables.find((v) => v.name === bindingName);
|
|
17650
17600
|
if (!variable) return false;
|
|
@@ -19923,44 +19873,38 @@ const convertHslToRgb = (hueDegrees, saturationPercent, lightnessPercent) => {
|
|
|
19923
19873
|
};
|
|
19924
19874
|
const parseColorToRgb = (value) => {
|
|
19925
19875
|
const trimmed = value.trim().toLowerCase().replace(/_/g, " ");
|
|
19926
|
-
|
|
19927
|
-
|
|
19928
|
-
|
|
19929
|
-
|
|
19930
|
-
|
|
19931
|
-
|
|
19932
|
-
|
|
19933
|
-
|
|
19934
|
-
|
|
19935
|
-
|
|
19936
|
-
|
|
19937
|
-
|
|
19938
|
-
|
|
19939
|
-
|
|
19940
|
-
|
|
19941
|
-
|
|
19942
|
-
|
|
19943
|
-
|
|
19944
|
-
|
|
19945
|
-
|
|
19946
|
-
|
|
19947
|
-
|
|
19948
|
-
|
|
19949
|
-
|
|
19950
|
-
|
|
19951
|
-
|
|
19952
|
-
|
|
19953
|
-
|
|
19954
|
-
|
|
19955
|
-
|
|
19956
|
-
|
|
19957
|
-
|
|
19958
|
-
};
|
|
19959
|
-
}
|
|
19960
|
-
if (trimmed.includes("hsl")) {
|
|
19961
|
-
const hslMatch = trimmed.match(/hsla?\(\s*([\d.]+)(?:deg)?[\s,]+([\d.]+)%[\s,]+([\d.]+)%/);
|
|
19962
|
-
if (hslMatch) return convertHslToRgb(parseFloat(hslMatch[1]), parseFloat(hslMatch[2]), parseFloat(hslMatch[3]));
|
|
19963
|
-
}
|
|
19876
|
+
const hex8Match = trimmed.match(/^#([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})[0-9a-f]{2}$/);
|
|
19877
|
+
if (hex8Match) return {
|
|
19878
|
+
red: parseInt(hex8Match[1], 16),
|
|
19879
|
+
green: parseInt(hex8Match[2], 16),
|
|
19880
|
+
blue: parseInt(hex8Match[3], 16)
|
|
19881
|
+
};
|
|
19882
|
+
const hex6Match = trimmed.match(/^#([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})$/);
|
|
19883
|
+
if (hex6Match) return {
|
|
19884
|
+
red: parseInt(hex6Match[1], 16),
|
|
19885
|
+
green: parseInt(hex6Match[2], 16),
|
|
19886
|
+
blue: parseInt(hex6Match[3], 16)
|
|
19887
|
+
};
|
|
19888
|
+
const hex4Match = trimmed.match(/^#([0-9a-f])([0-9a-f])([0-9a-f])[0-9a-f]$/);
|
|
19889
|
+
if (hex4Match) return {
|
|
19890
|
+
red: parseInt(hex4Match[1] + hex4Match[1], 16),
|
|
19891
|
+
green: parseInt(hex4Match[2] + hex4Match[2], 16),
|
|
19892
|
+
blue: parseInt(hex4Match[3] + hex4Match[3], 16)
|
|
19893
|
+
};
|
|
19894
|
+
const hex3Match = trimmed.match(/^#([0-9a-f])([0-9a-f])([0-9a-f])$/);
|
|
19895
|
+
if (hex3Match) return {
|
|
19896
|
+
red: parseInt(hex3Match[1] + hex3Match[1], 16),
|
|
19897
|
+
green: parseInt(hex3Match[2] + hex3Match[2], 16),
|
|
19898
|
+
blue: parseInt(hex3Match[3] + hex3Match[3], 16)
|
|
19899
|
+
};
|
|
19900
|
+
const rgbMatch = trimmed.match(/rgba?\(\s*(\d+)[\s,]+(\d+)[\s,]+(\d+)/);
|
|
19901
|
+
if (rgbMatch) return {
|
|
19902
|
+
red: parseInt(rgbMatch[1], 10),
|
|
19903
|
+
green: parseInt(rgbMatch[2], 10),
|
|
19904
|
+
blue: parseInt(rgbMatch[3], 10)
|
|
19905
|
+
};
|
|
19906
|
+
const hslMatch = trimmed.match(/hsla?\(\s*([\d.]+)(?:deg)?[\s,]+([\d.]+)%[\s,]+([\d.]+)%/);
|
|
19907
|
+
if (hslMatch) return convertHslToRgb(parseFloat(hslMatch[1]), parseFloat(hslMatch[2]), parseFloat(hslMatch[3]));
|
|
19964
19908
|
return null;
|
|
19965
19909
|
};
|
|
19966
19910
|
//#endregion
|
|
@@ -19988,18 +19932,9 @@ const extractColorFromShadowLayer = (layer) => {
|
|
|
19988
19932
|
if (hexMatch) return parseColorToRgb(`#${hexMatch[1]}`);
|
|
19989
19933
|
return null;
|
|
19990
19934
|
};
|
|
19991
|
-
const RGB_FUNCTION_PATTERN = /rgba?\([^)]*\)/g;
|
|
19992
|
-
const HEX_COLOR_PATTERN = /#[0-9a-f]{3,8}\b/gi;
|
|
19993
|
-
const NUMERIC_TOKEN_PATTERN = /(\d+(?:\.\d+)?)(px)?/g;
|
|
19994
|
-
const SHADOW_BLUR_TOKEN_INDEX = 2;
|
|
19995
19935
|
const parseShadowLayerBlur = (layer) => {
|
|
19996
|
-
const
|
|
19997
|
-
|
|
19998
|
-
for (const match of withoutColors.matchAll(NUMERIC_TOKEN_PATTERN)) {
|
|
19999
|
-
if (tokenIndex === SHADOW_BLUR_TOKEN_INDEX) return parseFloat(match[1]);
|
|
20000
|
-
tokenIndex += 1;
|
|
20001
|
-
}
|
|
20002
|
-
return 0;
|
|
19936
|
+
const numericTokens = [...layer.replace(/rgba?\([^)]*\)/g, "").replace(/#[0-9a-f]{3,8}\b/gi, "").matchAll(/(\d+(?:\.\d+)?)(px)?/g)].map((match) => parseFloat(match[1]));
|
|
19937
|
+
return numericTokens.length >= 3 ? numericTokens[2] : 0;
|
|
20003
19938
|
};
|
|
20004
19939
|
const hasColoredGlowShadow = (shadowValue) => {
|
|
20005
19940
|
for (const layer of splitShadowLayers(shadowValue)) {
|
|
@@ -20119,10 +20054,7 @@ const isIntrinsicJsxAttribute = (node) => {
|
|
|
20119
20054
|
};
|
|
20120
20055
|
//#endregion
|
|
20121
20056
|
//#region src/plugin/rules/state-and-effects/utils/is-controlled-prop-mirror.ts
|
|
20122
|
-
const componentPropNamesCache = /* @__PURE__ */ new WeakMap();
|
|
20123
20057
|
const collectComponentPropNames = (componentFunction) => {
|
|
20124
|
-
const cached = componentPropNamesCache.get(componentFunction);
|
|
20125
|
-
if (cached) return cached;
|
|
20126
20058
|
const propNames = /* @__PURE__ */ new Set();
|
|
20127
20059
|
if (!isFunctionLike$1(componentFunction)) return propNames;
|
|
20128
20060
|
const propsObjectParamNames = /* @__PURE__ */ new Set();
|
|
@@ -20136,23 +20068,27 @@ const collectComponentPropNames = (componentFunction) => {
|
|
|
20136
20068
|
if (child !== componentBody && isFunctionLike$1(child)) return false;
|
|
20137
20069
|
if (isNodeOfType(child, "VariableDeclarator") && isNodeOfType(child.id, "ObjectPattern") && isNodeOfType(child.init, "Identifier") && propsObjectParamNames.has(child.init.name)) collectPatternNames(child.id, propNames);
|
|
20138
20070
|
});
|
|
20139
|
-
componentPropNamesCache.set(componentFunction, propNames);
|
|
20140
20071
|
return propNames;
|
|
20141
20072
|
};
|
|
20142
|
-
const
|
|
20143
|
-
const getOwnScopeBoundNames = (functionNode) => {
|
|
20144
|
-
const cached = ownScopeBoundNamesCache.get(functionNode);
|
|
20145
|
-
if (cached) return cached;
|
|
20073
|
+
const declaresBindingNamed = (functionNode, bindingName) => {
|
|
20146
20074
|
const boundNames = /* @__PURE__ */ new Set();
|
|
20147
20075
|
if (isFunctionLike$1(functionNode)) for (const param of functionNode.params ?? []) collectPatternNames(param, boundNames);
|
|
20076
|
+
if (boundNames.has(bindingName)) return true;
|
|
20077
|
+
let declaresName = false;
|
|
20148
20078
|
walkAst(functionNode, (child) => {
|
|
20079
|
+
if (declaresName) return false;
|
|
20149
20080
|
if (child !== functionNode && isFunctionLike$1(child)) return false;
|
|
20150
|
-
if (isNodeOfType(child, "VariableDeclarator"))
|
|
20081
|
+
if (isNodeOfType(child, "VariableDeclarator")) {
|
|
20082
|
+
const declaratorNames = /* @__PURE__ */ new Set();
|
|
20083
|
+
collectPatternNames(child.id, declaratorNames);
|
|
20084
|
+
if (declaratorNames.has(bindingName)) {
|
|
20085
|
+
declaresName = true;
|
|
20086
|
+
return false;
|
|
20087
|
+
}
|
|
20088
|
+
}
|
|
20151
20089
|
});
|
|
20152
|
-
|
|
20153
|
-
return boundNames;
|
|
20090
|
+
return declaresName;
|
|
20154
20091
|
};
|
|
20155
|
-
const declaresBindingNamed = (functionNode, bindingName) => getOwnScopeBoundNames(functionNode).has(bindingName);
|
|
20156
20092
|
const isPropertyNamePosition = (identifier) => {
|
|
20157
20093
|
const parent = identifier.parent;
|
|
20158
20094
|
if (!parent) return false;
|
|
@@ -20575,28 +20511,36 @@ const isHandlerShapedReseed = (setterCall, componentFunction) => {
|
|
|
20575
20511
|
}
|
|
20576
20512
|
return hasNestedFunction;
|
|
20577
20513
|
};
|
|
20578
|
-
const
|
|
20579
|
-
|
|
20580
|
-
|
|
20581
|
-
|
|
20582
|
-
|
|
20583
|
-
|
|
20584
|
-
|
|
20514
|
+
const isReseededDraftBuffer = (useStateCall, isPropName) => {
|
|
20515
|
+
const setterName = getStateSetterName(useStateCall);
|
|
20516
|
+
if (!setterName) return false;
|
|
20517
|
+
const componentFunction = findEnclosingFunction(useStateCall);
|
|
20518
|
+
if (!componentFunction) return false;
|
|
20519
|
+
let isReseeded = false;
|
|
20520
|
+
walkAst(componentFunction, (child) => {
|
|
20521
|
+
if (isReseeded) return false;
|
|
20522
|
+
if (isNodeOfType(child, "CallExpression") && isNodeOfType(child.callee, "Identifier") && child.callee.name === setterName && isPropDerivedArgument(child.arguments?.[0], isPropName) && isHandlerShapedReseed(child, componentFunction)) {
|
|
20523
|
+
isReseeded = true;
|
|
20524
|
+
return false;
|
|
20525
|
+
}
|
|
20526
|
+
});
|
|
20527
|
+
return isReseeded;
|
|
20585
20528
|
};
|
|
20586
|
-
const
|
|
20529
|
+
const isAdjustedDuringRender = (useStateCall, isPropName) => {
|
|
20587
20530
|
const setterName = getStateSetterName(useStateCall);
|
|
20588
20531
|
if (!setterName) return false;
|
|
20589
20532
|
const componentFunction = findEnclosingFunction(useStateCall);
|
|
20590
20533
|
if (!componentFunction) return false;
|
|
20591
|
-
let
|
|
20534
|
+
let isAdjusted = false;
|
|
20592
20535
|
walkAst(componentFunction, (child) => {
|
|
20593
|
-
if (
|
|
20594
|
-
if (
|
|
20595
|
-
|
|
20536
|
+
if (isAdjusted) return false;
|
|
20537
|
+
if (child !== componentFunction && isFunctionLike$1(child)) return false;
|
|
20538
|
+
if (isNodeOfType(child, "CallExpression") && isNodeOfType(child.callee, "Identifier") && child.callee.name === setterName && isPropDerivedArgument(child.arguments?.[0], isPropName)) {
|
|
20539
|
+
isAdjusted = true;
|
|
20596
20540
|
return false;
|
|
20597
20541
|
}
|
|
20598
20542
|
});
|
|
20599
|
-
return
|
|
20543
|
+
return isAdjusted;
|
|
20600
20544
|
};
|
|
20601
20545
|
const noDerivedUseState = defineRule({
|
|
20602
20546
|
id: "no-derived-useState",
|
|
@@ -20613,7 +20557,8 @@ const noDerivedUseState = defineRule({
|
|
|
20613
20557
|
const initializer = node.arguments[0];
|
|
20614
20558
|
if (isNodeOfType(initializer, "Identifier") && propStackTracker.isPropName(initializer.name)) {
|
|
20615
20559
|
if (isInitialOnlyPropName(initializer.name)) return;
|
|
20616
|
-
if (
|
|
20560
|
+
if (isReseededDraftBuffer(node, propStackTracker.isPropName)) return;
|
|
20561
|
+
if (isAdjustedDuringRender(node, propStackTracker.isPropName)) return;
|
|
20617
20562
|
context.report({
|
|
20618
20563
|
node,
|
|
20619
20564
|
message: `Your users see a stale value when prop "${initializer.name}" changes because useState copies it once.`
|
|
@@ -20624,7 +20569,8 @@ const noDerivedUseState = defineRule({
|
|
|
20624
20569
|
const rootIdentifierName = getRootIdentifierName(initializer);
|
|
20625
20570
|
if (rootIdentifierName && propStackTracker.isPropName(rootIdentifierName)) {
|
|
20626
20571
|
if (isNodeOfType(initializer.property, "Identifier") && isInitialOnlyPropName(initializer.property.name)) return;
|
|
20627
|
-
if (
|
|
20572
|
+
if (isReseededDraftBuffer(node, propStackTracker.isPropName)) return;
|
|
20573
|
+
if (isAdjustedDuringRender(node, propStackTracker.isPropName)) return;
|
|
20628
20574
|
context.report({
|
|
20629
20575
|
node,
|
|
20630
20576
|
message: `Your users see a stale value when prop "${rootIdentifierName}" changes because useState copies it once.`
|
|
@@ -22323,20 +22269,20 @@ const getTriggerGuardRootName = (testNode) => {
|
|
|
22323
22269
|
return null;
|
|
22324
22270
|
};
|
|
22325
22271
|
const collectHandlerOnlyWriteStateNames = (componentBody, useStateBindings, handlerBindingNames) => {
|
|
22326
|
-
const setterNames = new Set(useStateBindings.map((binding) => binding.setterName));
|
|
22327
|
-
const settersWithAnyCall = /* @__PURE__ */ new Set();
|
|
22328
|
-
const settersWithNonHandlerCall = /* @__PURE__ */ new Set();
|
|
22329
|
-
walkAst(componentBody, (child) => {
|
|
22330
|
-
if (!isNodeOfType(child, "CallExpression")) return;
|
|
22331
|
-
if (!isNodeOfType(child.callee, "Identifier")) return;
|
|
22332
|
-
const setterName = child.callee.name;
|
|
22333
|
-
if (!setterNames.has(setterName)) return;
|
|
22334
|
-
settersWithAnyCall.add(setterName);
|
|
22335
|
-
if (settersWithNonHandlerCall.has(setterName)) return;
|
|
22336
|
-
if (!isInsideEventHandler(child, handlerBindingNames)) settersWithNonHandlerCall.add(setterName);
|
|
22337
|
-
});
|
|
22338
22272
|
const handlerOnlyWriteStateNames = /* @__PURE__ */ new Set();
|
|
22339
|
-
for (const binding of useStateBindings)
|
|
22273
|
+
for (const binding of useStateBindings) {
|
|
22274
|
+
let didFindAnySetterCall = false;
|
|
22275
|
+
let areAllSetterCallsInHandlers = true;
|
|
22276
|
+
walkAst(componentBody, (child) => {
|
|
22277
|
+
if (!areAllSetterCallsInHandlers) return false;
|
|
22278
|
+
if (!isNodeOfType(child, "CallExpression")) return;
|
|
22279
|
+
if (!isNodeOfType(child.callee, "Identifier")) return;
|
|
22280
|
+
if (child.callee.name !== binding.setterName) return;
|
|
22281
|
+
didFindAnySetterCall = true;
|
|
22282
|
+
if (!isInsideEventHandler(child, handlerBindingNames)) areAllSetterCallsInHandlers = false;
|
|
22283
|
+
});
|
|
22284
|
+
if (didFindAnySetterCall && areAllSetterCallsInHandlers) handlerOnlyWriteStateNames.add(binding.valueName);
|
|
22285
|
+
}
|
|
22340
22286
|
return handlerOnlyWriteStateNames;
|
|
22341
22287
|
};
|
|
22342
22288
|
const noEventTriggerState = defineRule({
|
|
@@ -22676,10 +22622,6 @@ const COLORED_BG_PATTERN = /^bg-(?:red|orange|amber|yellow|lime|green|emerald|te
|
|
|
22676
22622
|
const TEXT_COLOR_PATTERN = /^text-(?:white|black|transparent|current|inherit|\[|(?:gray|slate|zinc|neutral|stone|red|orange|amber|yellow|lime|green|emerald|teal|cyan|sky|blue|indigo|violet|purple|fuchsia|pink|rose)-)/;
|
|
22677
22623
|
const BG_COLOR_PATTERN = /^bg-(?:white|black|transparent|current|inherit|\[|(?:gray|slate|zinc|neutral|stone|red|orange|amber|yellow|lime|green|emerald|teal|cyan|sky|blue|indigo|violet|purple|fuchsia|pink|rose)-)/;
|
|
22678
22624
|
const splitVariantScope = (token) => {
|
|
22679
|
-
if (!token.includes(":")) return {
|
|
22680
|
-
scope: "",
|
|
22681
|
-
utility: token.startsWith("!") ? token.slice(1) : token
|
|
22682
|
-
};
|
|
22683
22625
|
const segments = token.split(":");
|
|
22684
22626
|
const rawUtility = segments[segments.length - 1];
|
|
22685
22627
|
return {
|
|
@@ -22703,7 +22645,6 @@ const noGrayOnColoredBackground = defineRule({
|
|
|
22703
22645
|
const bgColorScopes = /* @__PURE__ */ new Set();
|
|
22704
22646
|
for (const token of classStr.split(/\s+/)) {
|
|
22705
22647
|
if (!token) continue;
|
|
22706
|
-
if (!token.includes("text-") && !token.includes("bg-")) continue;
|
|
22707
22648
|
const { scope, utility } = splitVariantScope(token);
|
|
22708
22649
|
if (TEXT_COLOR_PATTERN.test(utility)) textColorScopes.add(scope);
|
|
22709
22650
|
if (BG_COLOR_PATTERN.test(utility)) bgColorScopes.add(scope);
|
|
@@ -23433,8 +23374,6 @@ const hasInfiniteIterationCount = (properties) => properties.some((property) =>
|
|
|
23433
23374
|
return isNodeOfType(property, "Property") && isNodeOfType(property.value, "Identifier") && property.value.name === "Infinity";
|
|
23434
23375
|
});
|
|
23435
23376
|
const isInfiniteAnimationSegment = (segment) => segment.trim().split(/\s+/).includes("infinite");
|
|
23436
|
-
const DURATION_SEGMENT_PATTERN = /^([\d.]+)(m?s)$/;
|
|
23437
|
-
const FIRST_TIME_TOKEN_PATTERN = /(?<![a-zA-Z\d])([\d.]+)(m?s)(?![a-zA-Z\d-])/;
|
|
23438
23377
|
const noLongTransitionDuration = defineRule({
|
|
23439
23378
|
id: "no-long-transition-duration",
|
|
23440
23379
|
title: "Transition duration too long",
|
|
@@ -23458,10 +23397,11 @@ const noLongTransitionDuration = defineRule({
|
|
|
23458
23397
|
if (key === "transitionDuration" || key === "animationDuration") {
|
|
23459
23398
|
let longestDurationPropertyMs = 0;
|
|
23460
23399
|
for (const segment of value.split(",")) {
|
|
23461
|
-
const
|
|
23462
|
-
|
|
23463
|
-
const
|
|
23464
|
-
longestDurationPropertyMs = Math.max(longestDurationPropertyMs,
|
|
23400
|
+
const trimmedSegment = segment.trim();
|
|
23401
|
+
const msMatch = trimmedSegment.match(/^([\d.]+)ms$/);
|
|
23402
|
+
const secondsMatch = trimmedSegment.match(/^([\d.]+)s$/);
|
|
23403
|
+
if (msMatch) longestDurationPropertyMs = Math.max(longestDurationPropertyMs, parseFloat(msMatch[1]));
|
|
23404
|
+
else if (secondsMatch) longestDurationPropertyMs = Math.max(longestDurationPropertyMs, parseFloat(secondsMatch[1]) * 1e3);
|
|
23465
23405
|
}
|
|
23466
23406
|
if (longestDurationPropertyMs > 0) durationMs = longestDurationPropertyMs;
|
|
23467
23407
|
}
|
|
@@ -23469,7 +23409,7 @@ const noLongTransitionDuration = defineRule({
|
|
|
23469
23409
|
let longestDurationMs = 0;
|
|
23470
23410
|
for (const segment of value.split(",")) {
|
|
23471
23411
|
if (key === "animation" && isInfiniteAnimationSegment(segment)) continue;
|
|
23472
|
-
const firstTimeMatch = segment.match(
|
|
23412
|
+
const firstTimeMatch = segment.match(/(?<![a-zA-Z\d])([\d.]+)(m?s)(?![a-zA-Z\d-])/);
|
|
23473
23413
|
if (!firstTimeMatch) continue;
|
|
23474
23414
|
const segmentDurationMs = firstTimeMatch[2] === "ms" ? parseFloat(firstTimeMatch[1]) : parseFloat(firstTimeMatch[1]) * 1e3;
|
|
23475
23415
|
longestDurationMs = Math.max(longestDurationMs, segmentDurationMs);
|
|
@@ -24641,14 +24581,14 @@ const collectRoleBranches = (expression, out) => {
|
|
|
24641
24581
|
out.hasOpaqueBranch = true;
|
|
24642
24582
|
};
|
|
24643
24583
|
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
|
|
24584
|
+
const INTERACTIVE_HANDLERS = [
|
|
24645
24585
|
"onClick",
|
|
24646
24586
|
"onMouseDown",
|
|
24647
24587
|
"onMouseUp",
|
|
24648
24588
|
"onKeyDown",
|
|
24649
24589
|
"onKeyPress",
|
|
24650
24590
|
"onKeyUp"
|
|
24651
|
-
]
|
|
24591
|
+
];
|
|
24652
24592
|
const noNoninteractiveElementInteractions = defineRule({
|
|
24653
24593
|
id: "no-noninteractive-element-interactions",
|
|
24654
24594
|
title: "Handler on non-interactive element",
|
|
@@ -24663,16 +24603,7 @@ const noNoninteractiveElementInteractions = defineRule({
|
|
|
24663
24603
|
const tag = getElementType(node, context.settings);
|
|
24664
24604
|
if (!NON_INTERACTIVE_ELEMENTS.has(tag)) return;
|
|
24665
24605
|
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;
|
|
24606
|
+
if (!INTERACTIVE_HANDLERS.some((handler) => hasJsxPropIgnoreCase(node.attributes, handler))) return;
|
|
24676
24607
|
if (isHiddenFromScreenReader(node, context.settings)) return;
|
|
24677
24608
|
const roleAttr = hasJsxPropIgnoreCase(node.attributes, "role");
|
|
24678
24609
|
if (roleAttr) {
|
|
@@ -25985,22 +25916,16 @@ const ELEMENT_ROLE_PAIRS = [
|
|
|
25985
25916
|
["tr", "row"],
|
|
25986
25917
|
["ul", "list"]
|
|
25987
25918
|
];
|
|
25988
|
-
const
|
|
25989
|
-
const
|
|
25990
|
-
const
|
|
25991
|
-
|
|
25992
|
-
|
|
25993
|
-
|
|
25994
|
-
|
|
25995
|
-
|
|
25996
|
-
|
|
25997
|
-
}
|
|
25998
|
-
return lookup;
|
|
25919
|
+
const getElementImplicitRoles = (tag) => {
|
|
25920
|
+
const out = [];
|
|
25921
|
+
for (const [element, role] of ELEMENT_ROLE_PAIRS) if (element === tag && !out.includes(role)) out.push(role);
|
|
25922
|
+
return out;
|
|
25923
|
+
};
|
|
25924
|
+
const getTagsForRole = (role) => {
|
|
25925
|
+
const out = [];
|
|
25926
|
+
for (const [element, r] of ELEMENT_ROLE_PAIRS) if (r === role && !out.includes(element)) out.push(element);
|
|
25927
|
+
return out;
|
|
25999
25928
|
};
|
|
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
25929
|
//#endregion
|
|
26005
25930
|
//#region src/plugin/utils/get-implicit-role.ts
|
|
26006
25931
|
const getImplicitRole = (node, elementType) => {
|
|
@@ -26500,7 +26425,6 @@ const TANSTACK_ROUTE_PROPERTY_ORDER = [
|
|
|
26500
26425
|
"headers",
|
|
26501
26426
|
"remountDeps"
|
|
26502
26427
|
];
|
|
26503
|
-
const TANSTACK_ROUTE_PROPERTY_INDEX = new Map(TANSTACK_ROUTE_PROPERTY_ORDER.map((propertyName, orderIndex) => [propertyName, orderIndex]));
|
|
26504
26428
|
const TANSTACK_ROUTE_CREATION_FUNCTIONS = new Set([
|
|
26505
26429
|
"createFileRoute",
|
|
26506
26430
|
"createRoute",
|
|
@@ -26516,7 +26440,6 @@ const TANSTACK_MIDDLEWARE_METHOD_ORDER = [
|
|
|
26516
26440
|
"server",
|
|
26517
26441
|
"handler"
|
|
26518
26442
|
];
|
|
26519
|
-
const TANSTACK_MIDDLEWARE_METHOD_INDEX = new Map(TANSTACK_MIDDLEWARE_METHOD_ORDER.map((methodName, orderIndex) => [methodName, orderIndex]));
|
|
26520
26443
|
const TANSTACK_REDIRECT_FUNCTIONS = new Set(["redirect", "notFound"]);
|
|
26521
26444
|
const TANSTACK_SERVER_FN_FILE_PATTERN = /\.functions(\.[jt]sx?)?$/;
|
|
26522
26445
|
const TANSTACK_QUERY_HOOKS = new Set([
|
|
@@ -27221,21 +27144,14 @@ const noStaticElementInteractions = defineRule({
|
|
|
27221
27144
|
category: "Accessibility",
|
|
27222
27145
|
create: (context) => {
|
|
27223
27146
|
const settings = resolveSettings$12(context.settings);
|
|
27224
|
-
const handlersLower = new Set(settings.handlers.map((handlerName) => handlerName.toLowerCase()));
|
|
27225
27147
|
const isTestlikeFile = isTestlikeFilename(context.filename);
|
|
27226
27148
|
return { JSXOpeningElement(node) {
|
|
27227
27149
|
if (isTestlikeFile) return;
|
|
27228
27150
|
let hasNonBlockerHandler = false;
|
|
27229
27151
|
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);
|
|
27152
|
+
for (const handler of settings.handlers) {
|
|
27153
|
+
const attribute = hasJsxPropIgnoreCase(node.attributes, handler);
|
|
27154
|
+
if (!attribute) continue;
|
|
27239
27155
|
if (isNullValue(attribute)) continue;
|
|
27240
27156
|
hasAnyHandler = true;
|
|
27241
27157
|
if (!isPureEventBlockerHandler(attribute)) {
|
|
@@ -28416,13 +28332,13 @@ const DOM_ATTRIBUTES_TO_CAMEL = new Map([
|
|
|
28416
28332
|
["xml:lang", "xmlLang"],
|
|
28417
28333
|
["xml:space", "xmlSpace"]
|
|
28418
28334
|
]);
|
|
28419
|
-
const
|
|
28335
|
+
const DOM_PROPERTIES_IGNORE_CASE = [
|
|
28420
28336
|
"charset",
|
|
28421
28337
|
"allowFullScreen",
|
|
28422
28338
|
"webkitAllowFullScreen",
|
|
28423
28339
|
"mozAllowFullScreen",
|
|
28424
28340
|
"webkitDirectory"
|
|
28425
|
-
]
|
|
28341
|
+
];
|
|
28426
28342
|
//#endregion
|
|
28427
28343
|
//#region src/plugin/constants/dom-property-tags.ts
|
|
28428
28344
|
const DOM_PROPERTY_TO_ALLOWED_TAGS = new Map([
|
|
@@ -28626,7 +28542,10 @@ const matchesHtmlTagConventions = (tagName) => {
|
|
|
28626
28542
|
if (!(firstCharacter >= 97 && firstCharacter <= 122)) return false;
|
|
28627
28543
|
return !tagName.includes("-");
|
|
28628
28544
|
};
|
|
28629
|
-
const normalizeAttributeCase = (name) =>
|
|
28545
|
+
const normalizeAttributeCase = (name) => {
|
|
28546
|
+
for (const ignoreCaseName of DOM_PROPERTIES_IGNORE_CASE) if (ignoreCaseName.toLowerCase() === name.toLowerCase()) return ignoreCaseName;
|
|
28547
|
+
return name;
|
|
28548
|
+
};
|
|
28630
28549
|
const hasUppercaseChar = (input) => /[A-Z]/.test(input);
|
|
28631
28550
|
const INVALID_PROP_ON_TAG = (propName, allowedTags) => `React ignores \`${propName}\` here because it only works on these tags: ${allowedTags}.`;
|
|
28632
28551
|
const DATA_LOWERCASE_REQUIRED = () => `React drops this \`data-*\` prop because of its capital letters.`;
|
|
@@ -32316,11 +32235,41 @@ const callsIdentifier = (root, identifierName) => {
|
|
|
32316
32235
|
});
|
|
32317
32236
|
return found;
|
|
32318
32237
|
};
|
|
32238
|
+
const setterIsCalledInAsyncContext = (componentBody, setterName) => {
|
|
32239
|
+
if (!componentBody) return false;
|
|
32240
|
+
let found = false;
|
|
32241
|
+
walkAst(componentBody, (child) => {
|
|
32242
|
+
if (found) return;
|
|
32243
|
+
if (!isFunctionLike$1(child)) return;
|
|
32244
|
+
const functionBody = child.body;
|
|
32245
|
+
if (!(Boolean(child.async) || hasOwnAwait(functionBody))) return;
|
|
32246
|
+
if (callsIdentifier(functionBody, setterName)) found = true;
|
|
32247
|
+
});
|
|
32248
|
+
return found;
|
|
32249
|
+
};
|
|
32319
32250
|
const PROMISE_CHAIN_METHOD_NAMES = new Set([
|
|
32320
32251
|
"then",
|
|
32321
32252
|
"catch",
|
|
32322
32253
|
"finally"
|
|
32323
32254
|
]);
|
|
32255
|
+
const setterIsCalledInPromiseChain = (componentBody, setterName) => {
|
|
32256
|
+
if (!componentBody) return false;
|
|
32257
|
+
let found = false;
|
|
32258
|
+
walkAst(componentBody, (child) => {
|
|
32259
|
+
if (found) return;
|
|
32260
|
+
if (!isNodeOfType(child, "CallExpression")) return;
|
|
32261
|
+
const callee = child.callee;
|
|
32262
|
+
if (!isNodeOfType(callee, "MemberExpression") || !isNodeOfType(callee.property, "Identifier") || !PROMISE_CHAIN_METHOD_NAMES.has(callee.property.name)) return;
|
|
32263
|
+
for (const argument of child.arguments ?? []) {
|
|
32264
|
+
if (!isFunctionLike$1(argument)) continue;
|
|
32265
|
+
if (callsIdentifier(argument.body, setterName)) {
|
|
32266
|
+
found = true;
|
|
32267
|
+
return;
|
|
32268
|
+
}
|
|
32269
|
+
}
|
|
32270
|
+
});
|
|
32271
|
+
return found;
|
|
32272
|
+
};
|
|
32324
32273
|
const ASYNC_DATA_CALLEE_NAMES = new Set([
|
|
32325
32274
|
"useApolloClient",
|
|
32326
32275
|
"useMutation",
|
|
@@ -32333,36 +32282,20 @@ const ASYNC_DATA_CALLEE_NAMES = new Set([
|
|
|
32333
32282
|
"fetch",
|
|
32334
32283
|
"axios"
|
|
32335
32284
|
]);
|
|
32336
|
-
const
|
|
32285
|
+
const referencesAsyncDataApi = (body) => {
|
|
32286
|
+
if (!body) return false;
|
|
32337
32287
|
let found = false;
|
|
32338
|
-
walkAst(
|
|
32339
|
-
if (found) return
|
|
32288
|
+
walkAst(body, (child) => {
|
|
32289
|
+
if (found) return;
|
|
32340
32290
|
if (isNodeOfType(child, "CallExpression")) {
|
|
32341
32291
|
const callee = child.callee;
|
|
32342
32292
|
if (isNodeOfType(callee, "Identifier") && ASYNC_DATA_CALLEE_NAMES.has(callee.name)) {
|
|
32343
32293
|
found = true;
|
|
32344
|
-
return
|
|
32345
|
-
}
|
|
32346
|
-
if (isNodeOfType(callee, "MemberExpression") && isNodeOfType(callee.property, "Identifier")) {
|
|
32347
|
-
if (ASYNC_DATA_CALLEE_NAMES.has(callee.property.name)) {
|
|
32348
|
-
found = true;
|
|
32349
|
-
return false;
|
|
32350
|
-
}
|
|
32351
|
-
if (setterName !== null && PROMISE_CHAIN_METHOD_NAMES.has(callee.property.name)) for (const argument of child.arguments ?? []) {
|
|
32352
|
-
if (!isFunctionLike$1(argument)) continue;
|
|
32353
|
-
if (callsIdentifier(argument.body, setterName)) {
|
|
32354
|
-
found = true;
|
|
32355
|
-
return false;
|
|
32356
|
-
}
|
|
32357
|
-
}
|
|
32294
|
+
return;
|
|
32358
32295
|
}
|
|
32359
|
-
|
|
32360
|
-
}
|
|
32361
|
-
if (setterName !== null && isFunctionLike$1(child)) {
|
|
32362
|
-
const functionBody = child.body;
|
|
32363
|
-
if ((Boolean(child.async) || hasOwnAwait(functionBody)) && callsIdentifier(functionBody, setterName)) {
|
|
32296
|
+
if (isNodeOfType(callee, "MemberExpression") && isNodeOfType(callee.property, "Identifier") && ASYNC_DATA_CALLEE_NAMES.has(callee.property.name)) {
|
|
32364
32297
|
found = true;
|
|
32365
|
-
return
|
|
32298
|
+
return;
|
|
32366
32299
|
}
|
|
32367
32300
|
}
|
|
32368
32301
|
});
|
|
@@ -32387,7 +32320,7 @@ const renderingUsetransitionLoading = defineRule({
|
|
|
32387
32320
|
const secondBinding = node.id.elements[1];
|
|
32388
32321
|
const setterName = isNodeOfType(secondBinding, "Identifier") ? secondBinding.name : null;
|
|
32389
32322
|
const fnBody = enclosingFunctionBody(node);
|
|
32390
|
-
if (fnBody &&
|
|
32323
|
+
if (fnBody && (setterName && setterIsCalledInAsyncContext(fnBody, setterName) || setterName && setterIsCalledInPromiseChain(fnBody, setterName) || referencesAsyncDataApi(fnBody))) return;
|
|
32391
32324
|
context.report({
|
|
32392
32325
|
node: node.init,
|
|
32393
32326
|
message: `This adds an extra render because useState for "${stateVariableName}" re-renders just for the loading flag, so if it's a state change & not a data fetch, use useTransition instead`
|
|
@@ -33181,17 +33114,17 @@ const rerenderStateOnlyInHandlers = defineRule({
|
|
|
33181
33114
|
const effectTriggerNames = /* @__PURE__ */ new Set();
|
|
33182
33115
|
for (const dependencyName of collectDependencyArrayNames(componentBody)) if (!(stateValueNames.has(dependencyName) && effectCallbackReadNames.has(dependencyName))) effectTriggerNames.add(dependencyName);
|
|
33183
33116
|
for (const reachableName of expandTransitiveDependencies(effectTriggerNames, dependencyGraph)) renderReachableNames.add(reachableName);
|
|
33184
|
-
const setterNames = new Set(bindings.map((binding) => binding.setterName));
|
|
33185
|
-
const calledSetterNames = /* @__PURE__ */ new Set();
|
|
33186
|
-
walkAst(componentBody, (child) => {
|
|
33187
|
-
if (isNodeOfType(child, "CallExpression") && isNodeOfType(child.callee, "Identifier") && setterNames.has(child.callee.name)) calledSetterNames.add(child.callee.name);
|
|
33188
|
-
});
|
|
33189
33117
|
for (const binding of bindings) {
|
|
33190
33118
|
if (renderReachableNames.has(binding.valueName)) continue;
|
|
33191
33119
|
if (binding.valueName === "_" || binding.valueName.startsWith("_")) continue;
|
|
33192
33120
|
const setterSuffix = binding.setterName.slice(3);
|
|
33193
33121
|
if (/^(TriggerRender|ForceUpdate|Rerender|ForceRender|Tick|Bump|BumpVersion|InvalidateRender|Refresh|Repaint)$/i.test(setterSuffix)) continue;
|
|
33194
|
-
|
|
33122
|
+
let setterCalled = false;
|
|
33123
|
+
walkAst(componentBody, (child) => {
|
|
33124
|
+
if (setterCalled) return;
|
|
33125
|
+
if (isNodeOfType(child, "CallExpression") && isNodeOfType(child.callee, "Identifier") && child.callee.name === binding.setterName) setterCalled = true;
|
|
33126
|
+
});
|
|
33127
|
+
if (!setterCalled) continue;
|
|
33195
33128
|
if (isSetterCalledDuringRender(componentBody, binding.setterName)) continue;
|
|
33196
33129
|
context.report({
|
|
33197
33130
|
node: binding.declarator,
|
|
@@ -34823,10 +34756,9 @@ const resolveTextBoundaryName = (openingElement) => {
|
|
|
34823
34756
|
if (isNodeOfType(openingElement.name, "JSXNamespacedName")) return openingElement.name.namespace.name;
|
|
34824
34757
|
return resolveJsxElementName(openingElement);
|
|
34825
34758
|
};
|
|
34826
|
-
const TEXT_COMPONENT_KEYWORDS = [...REACT_NATIVE_TEXT_COMPONENT_KEYWORDS];
|
|
34827
34759
|
const isTextHandlingComponent = (elementName) => {
|
|
34828
34760
|
if (REACT_NATIVE_TEXT_COMPONENTS.has(elementName)) return true;
|
|
34829
|
-
return
|
|
34761
|
+
return [...REACT_NATIVE_TEXT_COMPONENT_KEYWORDS].some((keyword) => elementName.includes(keyword));
|
|
34830
34762
|
};
|
|
34831
34763
|
const isTransparentTextWrapper = (elementName) => elementName !== null && REACT_NATIVE_TEXT_TRANSPARENT_COMPONENTS.has(elementName);
|
|
34832
34764
|
const isInsideTextHandlingComponent = (node) => {
|
|
@@ -40614,16 +40546,7 @@ const supabaseRlsPolicyRisk = defineRule({
|
|
|
40614
40546
|
//#endregion
|
|
40615
40547
|
//#region src/plugin/rules/security-scan/supabase-table-missing-rls.ts
|
|
40616
40548
|
const CREATE_PUBLIC_TABLE_PATTERN = /create\s+(?:unlogged\s+)?table\s+(?:if\s+not\s+exists\s+)?(?!(?:auth|storage|realtime|vault|extensions|graphql|graphql_public|pgbouncer|net|supabase_functions|supabase_migrations|cron|pgsodium|pgmq|information_schema|pg_catalog|pg_temp|private|internal)\s*\.)(?:public\s*\.\s*)?["`]?([A-Za-z_][\w$]*)["`]?(?:\s*\(|\s+as\b)/gi;
|
|
40617
|
-
const
|
|
40618
|
-
const collectLastEnableRlsIndexByTable = (content) => {
|
|
40619
|
-
const lastEnableIndexByTable = /* @__PURE__ */ new Map();
|
|
40620
|
-
for (const match of content.matchAll(ENABLE_RLS_PATTERN)) {
|
|
40621
|
-
const tableName = match[1];
|
|
40622
|
-
if (tableName === void 0) continue;
|
|
40623
|
-
lastEnableIndexByTable.set(tableName.toLowerCase(), match.index);
|
|
40624
|
-
}
|
|
40625
|
-
return lastEnableIndexByTable;
|
|
40626
|
-
};
|
|
40549
|
+
const enableRlsForTablePattern = (tableName) => new RegExp(`alter\\s+table\\s+(?:if\\s+exists\\s+)?(?:only\\s+)?(?:public\\s*\\.\\s*)?["\`]?${escapeRegExp(tableName)}["\`]?\\s+(?:force\\s+)?enable\\s+row\\s+level\\s+security`, "i");
|
|
40627
40550
|
const supabaseTableMissingRls = defineRule({
|
|
40628
40551
|
id: "supabase-table-missing-rls",
|
|
40629
40552
|
title: "Supabase table created without Row Level Security",
|
|
@@ -40634,13 +40557,11 @@ const supabaseTableMissingRls = defineRule({
|
|
|
40634
40557
|
const content = sanitizeSqlForScan(file.content);
|
|
40635
40558
|
if (!/create\s+(?:unlogged\s+)?table/i.test(content)) return [];
|
|
40636
40559
|
const findings = [];
|
|
40637
|
-
const lastEnableIndexByTable = collectLastEnableRlsIndexByTable(content);
|
|
40638
40560
|
CREATE_PUBLIC_TABLE_PATTERN.lastIndex = 0;
|
|
40639
40561
|
for (let match = CREATE_PUBLIC_TABLE_PATTERN.exec(content); match !== null; match = CREATE_PUBLIC_TABLE_PATTERN.exec(content)) {
|
|
40640
40562
|
const tableName = match[1];
|
|
40641
40563
|
if (tableName === void 0) continue;
|
|
40642
|
-
|
|
40643
|
-
if (lastEnableIndex !== void 0 && lastEnableIndex >= match.index) continue;
|
|
40564
|
+
if (enableRlsForTablePattern(tableName).test(content.slice(match.index))) continue;
|
|
40644
40565
|
const location = getLocationAtIndex(content, match.index);
|
|
40645
40566
|
findings.push({
|
|
40646
40567
|
message: "Supabase migration creates a public table but never enables Row Level Security, leaving every row exposed to the anon key.",
|
|
@@ -41330,10 +41251,10 @@ const tanstackStartRoutePropertyOrder = defineRule({
|
|
|
41330
41251
|
const propertyName = getPropertyKeyName(property);
|
|
41331
41252
|
if (propertyName !== null) orderedPropertyNames.push(propertyName);
|
|
41332
41253
|
}
|
|
41333
|
-
const sensitiveProperties = orderedPropertyNames.filter((propertyName) =>
|
|
41254
|
+
const sensitiveProperties = orderedPropertyNames.filter((propertyName) => TANSTACK_ROUTE_PROPERTY_ORDER.includes(propertyName));
|
|
41334
41255
|
let lastIndex = -1;
|
|
41335
41256
|
for (const propertyName of sensitiveProperties) {
|
|
41336
|
-
const currentIndex =
|
|
41257
|
+
const currentIndex = TANSTACK_ROUTE_PROPERTY_ORDER.indexOf(propertyName);
|
|
41337
41258
|
if (currentIndex < lastIndex) {
|
|
41338
41259
|
const expectedBefore = TANSTACK_ROUTE_PROPERTY_ORDER[lastIndex];
|
|
41339
41260
|
context.report({
|
|
@@ -41370,10 +41291,10 @@ const tanstackStartServerFnMethodOrder = defineRule({
|
|
|
41370
41291
|
} else return;
|
|
41371
41292
|
const ownMethodName = isNodeOfType(node.callee.property, "Identifier") ? node.callee.property.name : null;
|
|
41372
41293
|
if (methodNames[methodNames.length - 1] !== ownMethodName) return;
|
|
41373
|
-
const orderSensitiveMethods = methodNames.filter((name) =>
|
|
41294
|
+
const orderSensitiveMethods = methodNames.filter((name) => TANSTACK_MIDDLEWARE_METHOD_ORDER.includes(toMethodOrderToken(name)));
|
|
41374
41295
|
let lastIndex = -1;
|
|
41375
41296
|
for (const methodName of orderSensitiveMethods) {
|
|
41376
|
-
const currentIndex =
|
|
41297
|
+
const currentIndex = TANSTACK_MIDDLEWARE_METHOD_ORDER.indexOf(toMethodOrderToken(methodName));
|
|
41377
41298
|
if (currentIndex < lastIndex) {
|
|
41378
41299
|
const expectedBefore = TANSTACK_MIDDLEWARE_METHOD_ORDER[lastIndex];
|
|
41379
41300
|
context.report({
|
|
@@ -41664,14 +41585,7 @@ const getStaticPropertyName = (member) => {
|
|
|
41664
41585
|
if (member.computed && isNodeOfType(property, "Literal") && typeof property.value === "string") return property.value;
|
|
41665
41586
|
return null;
|
|
41666
41587
|
};
|
|
41667
|
-
const importInfoCache = /* @__PURE__ */ new WeakMap();
|
|
41668
41588
|
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
41589
|
const specifier = findVariableInitializer(identifier, identifier.name)?.initializer;
|
|
41676
41590
|
if (!specifier) return null;
|
|
41677
41591
|
const declaration = specifier.parent;
|