@pobammer-ts/eslint-cease-nonsense-rules 1.9.2 → 1.11.0

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.
Files changed (30) hide show
  1. package/README.md +139 -6
  2. package/dist/build-metadata.json +3 -3
  3. package/dist/index.d.ts +3 -1
  4. package/dist/index.d.ts.map +1 -1
  5. package/dist/index.js +678 -142
  6. package/dist/index.js.map +16 -10
  7. package/dist/rules/no-shorthand-names.d.ts +1 -0
  8. package/dist/rules/no-shorthand-names.d.ts.map +1 -1
  9. package/dist/rules/prefer-pattern-replacements.d.ts +13 -0
  10. package/dist/rules/prefer-pattern-replacements.d.ts.map +1 -0
  11. package/dist/rules/require-paired-calls.d.ts +18 -2
  12. package/dist/rules/require-paired-calls.d.ts.map +1 -1
  13. package/dist/rules/strict-component-boundaries.d.ts.map +1 -1
  14. package/dist/rules/use-exhaustive-dependencies.d.ts.map +1 -1
  15. package/dist/rules/use-hook-at-top-level.d.ts.map +1 -1
  16. package/dist/utilities/configure-utilities.d.ts +7 -0
  17. package/dist/utilities/configure-utilities.d.ts.map +1 -1
  18. package/dist/utilities/pattern-replacement/constant-folder.d.ts +24 -0
  19. package/dist/utilities/pattern-replacement/constant-folder.d.ts.map +1 -0
  20. package/dist/utilities/pattern-replacement/index.d.ts +7 -0
  21. package/dist/utilities/pattern-replacement/index.d.ts.map +1 -0
  22. package/dist/utilities/pattern-replacement/pattern-matcher.d.ts +62 -0
  23. package/dist/utilities/pattern-replacement/pattern-matcher.d.ts.map +1 -0
  24. package/dist/utilities/pattern-replacement/pattern-parser.d.ts +22 -0
  25. package/dist/utilities/pattern-replacement/pattern-parser.d.ts.map +1 -0
  26. package/dist/utilities/pattern-replacement/pattern-types.d.ts +69 -0
  27. package/dist/utilities/pattern-replacement/pattern-types.d.ts.map +1 -0
  28. package/dist/utilities/pattern-replacement/replacement-generator.d.ts +16 -0
  29. package/dist/utilities/pattern-replacement/replacement-generator.d.ts.map +1 -0
  30. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -18728,6 +18728,7 @@ var no_print_default = noPrint;
18728
18728
  import { TSESTree as TSESTree6 } from "@typescript-eslint/types";
18729
18729
  var isRuleOptions = Compile(build_default.Object({
18730
18730
  allowPropertyAccess: build_default.Optional(build_default.Array(build_default.String())),
18731
+ ignoreShorthands: build_default.Optional(build_default.Array(build_default.String())),
18731
18732
  shorthands: build_default.Optional(build_default.Record(build_default.String(), build_default.String()))
18732
18733
  }));
18733
18734
  function isRecord2(value) {
@@ -18735,6 +18736,7 @@ function isRecord2(value) {
18735
18736
  }
18736
18737
  var DEFAULT_OPTIONS2 = {
18737
18738
  allowPropertyAccess: ["char"],
18739
+ ignoreShorthands: [],
18738
18740
  shorthands: {
18739
18741
  args: "parameters",
18740
18742
  char: "character",
@@ -18744,32 +18746,78 @@ var DEFAULT_OPTIONS2 = {
18744
18746
  };
18745
18747
  var REGEX_PATTERN_MATCHER = regex2("^/(?<first>.+)/(?<second>[gimsuy]*)$");
18746
18748
  var WORD_BOUNDARY_REGEX = /(?<=[a-z])(?=[A-Z])|(?<=[A-Z])(?=[A-Z][a-z])|(?<=[a-zA-Z])(?=\d)|(?<=\d)(?=[a-zA-Z])/;
18749
+ var FUNT_PATTERN = regex2("[.+^${}()|[\\]\\\\]", "g");
18750
+ var SPLIT_CACHE = new Map;
18751
+ var MAX_SPLIT_CACHE_SIZE = 1024;
18747
18752
  function splitIdentifierIntoWords(identifier3) {
18748
- return identifier3.split(WORD_BOUNDARY_REGEX);
18753
+ const cached = SPLIT_CACHE.get(identifier3);
18754
+ if (cached !== undefined)
18755
+ return cached;
18756
+ const words = identifier3.split(WORD_BOUNDARY_REGEX);
18757
+ if (SPLIT_CACHE.size >= MAX_SPLIT_CACHE_SIZE) {
18758
+ const firstKey = SPLIT_CACHE.keys().next().value;
18759
+ if (firstKey !== undefined)
18760
+ SPLIT_CACHE.delete(firstKey);
18761
+ }
18762
+ SPLIT_CACHE.set(identifier3, words);
18763
+ return words;
18764
+ }
18765
+ function countCaptureGroups(replacement) {
18766
+ const matches = replacement.match(/\$(\d+)/g);
18767
+ if (matches === null)
18768
+ return 0;
18769
+ let maxGroup = 0;
18770
+ for (const dollarRef of matches) {
18771
+ const groupNum = Number.parseInt(dollarRef.slice(1), 10);
18772
+ if (groupNum > maxGroup)
18773
+ maxGroup = groupNum;
18774
+ }
18775
+ return maxGroup;
18776
+ }
18777
+ var REPLACEMENT_PATTERN_CACHE = new Map;
18778
+ function getReplacementPattern(index2) {
18779
+ let pattern4 = REPLACEMENT_PATTERN_CACHE.get(index2);
18780
+ if (pattern4 === undefined) {
18781
+ pattern4 = new RegExp(`\\$${index2}`, "g");
18782
+ REPLACEMENT_PATTERN_CACHE.set(index2, pattern4);
18783
+ }
18784
+ return pattern4;
18785
+ }
18786
+ function buildReplacementPatterns(replacement) {
18787
+ const count = countCaptureGroups(replacement);
18788
+ if (count === 0)
18789
+ return [];
18790
+ const patterns2 = new Array(count);
18791
+ for (let index2 = 1;index2 <= count; index2 += 1) {
18792
+ patterns2[index2 - 1] = getReplacementPattern(index2);
18793
+ }
18794
+ return patterns2;
18749
18795
  }
18750
18796
  function createMatcher(key, replacement) {
18751
18797
  if (key.startsWith("/")) {
18752
- const match = REGEX_PATTERN_MATCHER.exec(key);
18753
- if (match) {
18798
+ const match = key.match(REGEX_PATTERN_MATCHER);
18799
+ if (match?.groups) {
18754
18800
  return {
18755
18801
  matcher: {
18756
18802
  original: key,
18757
18803
  pattern: new RegExp(`^${match.groups.first}$`, match.groups.second),
18758
- replacement
18804
+ replacement,
18805
+ replacementPatterns: buildReplacementPatterns(replacement)
18759
18806
  },
18760
18807
  type: "pattern"
18761
18808
  };
18762
18809
  }
18763
18810
  }
18764
18811
  if (key.includes("*") || key.includes("?")) {
18765
- const regexPattern = key.replaceAll(/[.+^${}()|[\]\\]/g, String.raw`\$&`).replaceAll("*", "(.*)").replaceAll("?", "(.)");
18812
+ const regexPattern = key.replaceAll(FUNT_PATTERN, String.raw`\$&`).replaceAll("*", "(.*)").replaceAll("?", "(.)");
18766
18813
  let captureIndex = 0;
18767
18814
  const regexReplacement = replacement.replaceAll("*", () => `$${++captureIndex}`);
18768
18815
  return {
18769
18816
  matcher: {
18770
18817
  original: key,
18771
18818
  pattern: new RegExp(`^${regexPattern}$`),
18772
- replacement: regexReplacement
18819
+ replacement: regexReplacement,
18820
+ replacementPatterns: buildReplacementPatterns(regexReplacement)
18773
18821
  },
18774
18822
  type: "pattern"
18775
18823
  };
@@ -18784,6 +18832,7 @@ function matchWord(word, matchers, exactMatchers) {
18784
18832
  const exactReplacement = exactMatchers.get(word);
18785
18833
  if (exactReplacement !== undefined) {
18786
18834
  return {
18835
+ matchedWord: word,
18787
18836
  replacement: exactReplacement,
18788
18837
  shorthand: word
18789
18838
  };
@@ -18792,10 +18841,13 @@ function matchWord(word, matchers, exactMatchers) {
18792
18841
  const match = word.match(matcher.pattern);
18793
18842
  if (match) {
18794
18843
  let replaced = matcher.replacement;
18795
- for (let index2 = 1;index2 < match.length; index2 += 1) {
18796
- replaced = replaced.replaceAll(new RegExp(`\\$${index2}`, "g"), match[index2] ?? "");
18844
+ let captureIndex = 1;
18845
+ for (const replacementPattern of matcher.replacementPatterns) {
18846
+ replaced = replaced.replaceAll(replacementPattern, match[captureIndex] ?? "");
18847
+ captureIndex += 1;
18797
18848
  }
18798
18849
  return {
18850
+ matchedWord: word,
18799
18851
  replacement: replaced,
18800
18852
  shorthand: matcher.original
18801
18853
  };
@@ -18803,22 +18855,13 @@ function matchWord(word, matchers, exactMatchers) {
18803
18855
  }
18804
18856
  return;
18805
18857
  }
18806
- function buildReplacementIdentifier(identifier3, options3) {
18807
- const words = splitIdentifierIntoWords(identifier3);
18808
- const matches = new Array;
18809
- let hasMatch = false;
18810
- const newWords = words.map((word) => {
18811
- const match = matchWord(word, options3.matchers, options3.exactMatchers);
18812
- if (match) {
18813
- hasMatch = true;
18814
- matches.push(match);
18815
- return match.replacement;
18816
- }
18817
- return word;
18818
- });
18819
- if (!hasMatch)
18820
- return;
18821
- return { matches, replaced: newWords.join("") };
18858
+ function isWordIgnored(word, ignoreMatchers, ignoreExact) {
18859
+ if (ignoreExact.has(word))
18860
+ return true;
18861
+ for (const matcher of ignoreMatchers)
18862
+ if (matcher.pattern.test(word))
18863
+ return true;
18864
+ return false;
18822
18865
  }
18823
18866
  function normalizeOptions2(rawOptions) {
18824
18867
  const mergedShorthands = { ...DEFAULT_OPTIONS2.shorthands };
@@ -18836,29 +18879,112 @@ function normalizeOptions2(rawOptions) {
18836
18879
  matchers.push(result.matcher);
18837
18880
  }
18838
18881
  const allowPropertyAccessSource = rawOptions?.allowPropertyAccess ?? DEFAULT_OPTIONS2.allowPropertyAccess;
18882
+ const ignoreMatchers = new Array;
18883
+ const ignoreExact = new Set;
18884
+ for (const pattern4 of rawOptions?.ignoreShorthands ?? []) {
18885
+ const result = createMatcher(pattern4, "");
18886
+ if (result.type === "exact")
18887
+ ignoreExact.add(result.original);
18888
+ else
18889
+ ignoreMatchers.push(result.matcher);
18890
+ }
18839
18891
  return {
18840
18892
  allowPropertyAccess: new Set(allowPropertyAccessSource),
18841
18893
  exactMatchers,
18894
+ ignoreExact,
18895
+ ignoreMatchers,
18842
18896
  matchers,
18843
18897
  selector: "Identifier"
18844
18898
  };
18845
18899
  }
18900
+ var IMPORT_PARENT_TYPES = new Set(["ImportSpecifier", "ImportDefaultSpecifier", "ImportNamespaceSpecifier"]);
18846
18901
  var noShorthandNames = {
18847
18902
  create(context) {
18848
18903
  const validatedOptions = isRuleOptions.Check(context.options[0]) ? context.options[0] : undefined;
18849
18904
  const normalized = normalizeOptions2(validatedOptions);
18850
- const { allowPropertyAccess, selector } = normalized;
18905
+ const { allowPropertyAccess, ignoreMatchers, ignoreExact, selector, matchers, exactMatchers } = normalized;
18906
+ const identifierResultCache = new Map;
18907
+ const ignoredWordCache = new Map;
18908
+ function cachedIsWordIgnored(word) {
18909
+ const cached = ignoredWordCache.get(word);
18910
+ if (cached !== undefined)
18911
+ return cached;
18912
+ const result = isWordIgnored(word, ignoreMatchers, ignoreExact);
18913
+ ignoredWordCache.set(word, result);
18914
+ return result;
18915
+ }
18916
+ function getIdentifierResult(identifier3) {
18917
+ if (identifierResultCache.has(identifier3))
18918
+ return identifierResultCache.get(identifier3);
18919
+ const words = splitIdentifierIntoWords(identifier3);
18920
+ const matches = new Array;
18921
+ let hasMatch = false;
18922
+ for (const word of words) {
18923
+ const match = matchWord(word, matchers, exactMatchers);
18924
+ if (match) {
18925
+ hasMatch = true;
18926
+ matches.push(match);
18927
+ }
18928
+ }
18929
+ if (!hasMatch) {
18930
+ identifierResultCache.set(identifier3, undefined);
18931
+ return;
18932
+ }
18933
+ let replaced = "";
18934
+ let matchIndex = 0;
18935
+ for (const word of words) {
18936
+ const currentMatch = matches[matchIndex];
18937
+ if (currentMatch !== undefined && currentMatch.matchedWord === word) {
18938
+ replaced += currentMatch.replacement;
18939
+ matchIndex += 1;
18940
+ } else {
18941
+ replaced += word;
18942
+ }
18943
+ }
18944
+ const result = { matches, replaced };
18945
+ identifierResultCache.set(identifier3, result);
18946
+ return result;
18947
+ }
18851
18948
  return {
18852
18949
  [selector](node) {
18950
+ const { parent } = node;
18951
+ if (parent !== undefined && isRecord2(parent)) {
18952
+ const parentType = parent.type;
18953
+ if (IMPORT_PARENT_TYPES.has(parentType))
18954
+ return;
18955
+ }
18853
18956
  const identifierName = node.name;
18854
- const result = buildReplacementIdentifier(identifierName, normalized);
18957
+ const result = getIdentifierResult(identifierName);
18855
18958
  if (result === undefined)
18856
18959
  return;
18857
18960
  const { replaced, matches } = result;
18858
- const { parent } = node;
18859
- const [match] = matches;
18860
- if (matches.length === 1 && match !== undefined && allowPropertyAccess.has(match.shorthand) && parent !== undefined && isRecord2(parent) && parent.type === TSESTree6.AST_NODE_TYPES.MemberExpression && parent.property === node) {
18961
+ if (cachedIsWordIgnored(identifierName))
18861
18962
  return;
18963
+ let allIgnored = true;
18964
+ for (const match of matches) {
18965
+ if (!cachedIsWordIgnored(match.matchedWord)) {
18966
+ allIgnored = false;
18967
+ break;
18968
+ }
18969
+ }
18970
+ if (allIgnored)
18971
+ return;
18972
+ if (parent !== undefined && isRecord2(parent)) {
18973
+ const parentType = parent.type;
18974
+ const isPropertyAccess = parentType === "MemberExpression" && parent.property === node || parentType === "TSQualifiedName" && parent.right === node;
18975
+ if (isPropertyAccess) {
18976
+ if (allowPropertyAccess.has(identifierName))
18977
+ return;
18978
+ let allWordsAllowed = true;
18979
+ for (const match of matches) {
18980
+ if (!allowPropertyAccess.has(match.matchedWord)) {
18981
+ allWordsAllowed = false;
18982
+ break;
18983
+ }
18984
+ }
18985
+ if (allWordsAllowed)
18986
+ return;
18987
+ }
18862
18988
  }
18863
18989
  if (identifierName === "plr" && parent?.type === TSESTree6.AST_NODE_TYPES.VariableDeclarator && parent.id === node) {
18864
18990
  const { init } = parent;
@@ -18893,7 +19019,12 @@ var noShorthandNames = {
18893
19019
  additionalProperties: false,
18894
19020
  properties: {
18895
19021
  allowPropertyAccess: {
18896
- description: "Shorthand names that are allowed as property access",
19022
+ description: "Shorthand names allowed as property access or qualified names",
19023
+ items: { type: "string" },
19024
+ type: "array"
19025
+ },
19026
+ ignoreShorthands: {
19027
+ description: "Shorthand patterns to ignore completely (supports exact, glob, regex)",
18897
19028
  items: { type: "string" },
18898
19029
  type: "array"
18899
19030
  },
@@ -19636,14 +19767,414 @@ var preferPascalCaseEnums = {
19636
19767
  };
19637
19768
  var prefer_pascal_case_enums_default = preferPascalCaseEnums;
19638
19769
 
19639
- // src/rules/prefer-sequence-overloads.ts
19770
+ // src/utilities/pattern-replacement/pattern-matcher.ts
19771
+ import { AST_NODE_TYPES as AST_NODE_TYPES10 } from "@typescript-eslint/types";
19772
+
19773
+ // src/utilities/pattern-replacement/constant-folder.ts
19640
19774
  import { AST_NODE_TYPES as AST_NODE_TYPES9 } from "@typescript-eslint/types";
19775
+ function normalizeZero(num) {
19776
+ return Object.is(num, -0) ? 0 : num;
19777
+ }
19778
+ function unwrap(node) {
19779
+ switch (node.type) {
19780
+ case AST_NODE_TYPES9.TSAsExpression:
19781
+ case AST_NODE_TYPES9.TSNonNullExpression:
19782
+ return unwrap(node.expression);
19783
+ default:
19784
+ return node;
19785
+ }
19786
+ }
19787
+ function evaluateConstant(node) {
19788
+ const expression = unwrap(node);
19789
+ if (expression.type === AST_NODE_TYPES9.Literal && typeof expression.value === "number") {
19790
+ return normalizeZero(expression.value);
19791
+ }
19792
+ if (expression.type === AST_NODE_TYPES9.UnaryExpression) {
19793
+ const argument = evaluateConstant(expression.argument);
19794
+ if (argument === undefined)
19795
+ return;
19796
+ switch (expression.operator) {
19797
+ case "-":
19798
+ return normalizeZero(-argument);
19799
+ case "+":
19800
+ return normalizeZero(Number(argument));
19801
+ default:
19802
+ return;
19803
+ }
19804
+ }
19805
+ if (expression.type === AST_NODE_TYPES9.BinaryExpression) {
19806
+ if (expression.left.type === AST_NODE_TYPES9.PrivateIdentifier)
19807
+ return;
19808
+ const left = evaluateConstant(expression.left);
19809
+ const right = evaluateConstant(expression.right);
19810
+ if (left === undefined || right === undefined)
19811
+ return;
19812
+ let result;
19813
+ switch (expression.operator) {
19814
+ case "+":
19815
+ result = left + right;
19816
+ break;
19817
+ case "-":
19818
+ result = left - right;
19819
+ break;
19820
+ case "*":
19821
+ result = left * right;
19822
+ break;
19823
+ case "/":
19824
+ result = left / right;
19825
+ break;
19826
+ default:
19827
+ return;
19828
+ }
19829
+ return Number.isFinite(result) ? normalizeZero(result) : undefined;
19830
+ }
19831
+ return;
19832
+ }
19833
+
19834
+ // src/utilities/pattern-replacement/pattern-matcher.ts
19835
+ function buildPatternIndex(patterns2) {
19836
+ const index2 = new Map;
19837
+ for (const pattern4 of patterns2) {
19838
+ const key = pattern4.type === "constructor" ? `constructor:${pattern4.typeName}` : `staticMethod:${pattern4.typeName}:${pattern4.methodName}`;
19839
+ const existing = index2.get(key);
19840
+ if (existing)
19841
+ existing.push(pattern4);
19842
+ else
19843
+ index2.set(key, [pattern4]);
19844
+ }
19845
+ return index2;
19846
+ }
19847
+ function resolveCallee(node) {
19848
+ const callee = unwrap(node.callee);
19849
+ if (node.type === AST_NODE_TYPES10.NewExpression && callee.type === AST_NODE_TYPES10.Identifier) {
19850
+ return { kind: "constructor", typeName: callee.name };
19851
+ }
19852
+ if (node.type === AST_NODE_TYPES10.CallExpression) {
19853
+ const member = callee.type === AST_NODE_TYPES10.ChainExpression ? unwrap(callee.expression) : callee;
19854
+ if (member.type === AST_NODE_TYPES10.MemberExpression && !member.computed) {
19855
+ const object3 = unwrap(member.object);
19856
+ if (object3.type === AST_NODE_TYPES10.Identifier && member.property.type === AST_NODE_TYPES10.Identifier) {
19857
+ return {
19858
+ kind: "staticMethod",
19859
+ methodName: member.property.name,
19860
+ typeName: object3.name
19861
+ };
19862
+ }
19863
+ }
19864
+ }
19865
+ return { kind: "unknown" };
19866
+ }
19867
+ function captureParameter(node, sourceCode) {
19868
+ const expression = unwrap(node);
19869
+ const sourceText = sourceCode.getText(expression);
19870
+ const constValue = evaluateConstant(expression);
19871
+ let expressionKey;
19872
+ let isComplex = false;
19873
+ if (expression.type === AST_NODE_TYPES10.Literal && typeof expression.value === "number") {
19874
+ expressionKey = `literal:${normalizeZero(expression.value)}`;
19875
+ } else if (expression.type === AST_NODE_TYPES10.Identifier) {
19876
+ if (expression.name === "undefined")
19877
+ expressionKey = "undefined";
19878
+ else
19879
+ expressionKey = `id:${expression.name}`;
19880
+ } else if (constValue === undefined) {
19881
+ expressionKey = `complex:${sourceText}`;
19882
+ isComplex = true;
19883
+ } else
19884
+ expressionKey = `const:${constValue}`;
19885
+ if (constValue === undefined)
19886
+ return { expressionKey, isComplex, node: expression, sourceText };
19887
+ return { constValue, expressionKey, isComplex, node: expression, sourceText };
19888
+ }
19889
+ function matchParameters(patterns2, parameters3, sourceCode) {
19890
+ const captures = new Map;
19891
+ const optionalStart = patterns2.findIndex((parsedArg) => parsedArg.kind === "optional");
19892
+ const minimumParameters = optionalStart === -1 ? patterns2.length : optionalStart;
19893
+ if (parameters3.length < minimumParameters || parameters3.length > patterns2.length)
19894
+ return;
19895
+ for (let index2 = 0;index2 < patterns2.length; index2 += 1) {
19896
+ const pattern4 = patterns2[index2];
19897
+ if (pattern4 === undefined)
19898
+ continue;
19899
+ const parameter2 = parameters3[index2];
19900
+ const unwrappedParameter = parameter2 === undefined ? undefined : unwrap(parameter2);
19901
+ const isMissing = parameter2 === undefined || unwrappedParameter?.type === AST_NODE_TYPES10.Identifier && unwrappedParameter.name === "undefined";
19902
+ if (pattern4.kind === "literal") {
19903
+ if (isMissing)
19904
+ return;
19905
+ const captured = captureParameter(parameter2, sourceCode);
19906
+ if (captured.constValue !== pattern4.value)
19907
+ return;
19908
+ } else if (pattern4.kind === "optional") {
19909
+ if (isMissing)
19910
+ continue;
19911
+ const captured = captureParameter(parameter2, sourceCode);
19912
+ if (captured.constValue !== pattern4.value)
19913
+ return;
19914
+ } else if (pattern4.kind === "capture") {
19915
+ if (isMissing)
19916
+ return;
19917
+ const captured = captureParameter(parameter2, sourceCode);
19918
+ const captureName = pattern4.name;
19919
+ const existing = captures.get(captureName);
19920
+ if (existing !== undefined && existing.expressionKey !== captured.expressionKey)
19921
+ return;
19922
+ captures.set(captureName, captured);
19923
+ } else if (pattern4.kind === "wildcard" && isMissing)
19924
+ return;
19925
+ }
19926
+ return captures;
19927
+ }
19928
+ var CONDITION_PATTERN = regex2("^(?<operator>[!<>=]+)\\s*(?<target>.+)$");
19929
+ function parseCondition(condition) {
19930
+ const match = CONDITION_PATTERN.exec(condition);
19931
+ return match ? [match.groups.operator, match.groups.target] : ["==", "0"];
19932
+ }
19933
+ function evaluateConditions(conditions, captures) {
19934
+ for (const [name, condition] of conditions) {
19935
+ const captured = captures.get(name);
19936
+ if (captured?.constValue === undefined)
19937
+ return false;
19938
+ const value = captured.constValue;
19939
+ const [operator, targetStr] = parseCondition(condition);
19940
+ const target2 = Number.parseFloat(targetStr);
19941
+ if (!Number.isFinite(target2))
19942
+ return false;
19943
+ let passes;
19944
+ switch (operator) {
19945
+ case "!=":
19946
+ passes = value !== target2;
19947
+ break;
19948
+ case "==":
19949
+ passes = value === target2;
19950
+ break;
19951
+ case ">":
19952
+ passes = value > target2;
19953
+ break;
19954
+ case "<":
19955
+ passes = value < target2;
19956
+ break;
19957
+ case ">=":
19958
+ passes = value >= target2;
19959
+ break;
19960
+ case "<=":
19961
+ passes = value <= target2;
19962
+ break;
19963
+ default:
19964
+ passes = false;
19965
+ }
19966
+ if (!passes)
19967
+ return false;
19968
+ }
19969
+ return true;
19970
+ }
19971
+ function canSafelySubstitute(captures) {
19972
+ for (const [, captured] of captures)
19973
+ if (captured.isComplex)
19974
+ return false;
19975
+ return true;
19976
+ }
19977
+ // src/utilities/pattern-replacement/pattern-parser.ts
19978
+ var CONSTRUCTOR_PATTERN = regex2("^new\\s+(?<typeName>\\w+)\\((?<argumentsString>.*)\\)$");
19979
+ var STATIC_METHOD_PATTERN = regex2("^(?<typeName>\\w+)\\.(?<methodName>\\w+)\\((?<argumentsString>.*)\\)$");
19980
+ var STATIC_ACCESS_PATTERN = regex2("^(?<typeName>\\w+)\\.(?<property>\\w+)$");
19981
+ var CALL_PATTERN = regex2("^(?<name>\\w+)\\((?<argumentsString>.*)\\)$");
19982
+ function parseParameters(parametersString) {
19983
+ const trimmed = parametersString.trim();
19984
+ if (trimmed === "")
19985
+ return [];
19986
+ const parameters3 = trimmed.split(",").map((parameter2) => parameter2.trim());
19987
+ const result = new Array;
19988
+ let size = 0;
19989
+ for (const parameter2 of parameters3) {
19990
+ if (parameter2 === "_")
19991
+ result[size++] = { kind: "wildcard" };
19992
+ else if (parameter2.startsWith("$"))
19993
+ result[size++] = { kind: "capture", name: parameter2.slice(1) };
19994
+ else if (parameter2.endsWith("?")) {
19995
+ const value = Number.parseFloat(parameter2.slice(0, -1));
19996
+ result[size++] = { kind: "optional", value };
19997
+ } else {
19998
+ const value = Number.parseFloat(parameter2);
19999
+ result[size++] = { kind: "literal", value };
20000
+ }
20001
+ }
20002
+ return result;
20003
+ }
20004
+ function parseReplacement(replacement) {
20005
+ const staticMatch = STATIC_ACCESS_PATTERN.exec(replacement);
20006
+ if (staticMatch && !replacement.includes("("))
20007
+ return { ...staticMatch.groups, kind: "staticAccess" };
20008
+ const callMatch = CALL_PATTERN.exec(replacement);
20009
+ if (callMatch) {
20010
+ const { name, argumentsString } = callMatch.groups;
20011
+ const parameters3 = argumentsString.trim() === "" ? [] : argumentsString.split(",").map((value) => value.trim());
20012
+ return { kind: "call", name, parameters: parameters3 };
20013
+ }
20014
+ return { kind: "identifier", name: replacement };
20015
+ }
20016
+ function parsePattern(match, replacement, when) {
20017
+ const conditions = new Map;
20018
+ if (when)
20019
+ for (const [key, value] of Object.entries(when))
20020
+ conditions.set(key, value);
20021
+ const constructorMatch = CONSTRUCTOR_PATTERN.exec(match);
20022
+ if (constructorMatch) {
20023
+ const { typeName, argumentsString } = constructorMatch.groups;
20024
+ return {
20025
+ conditions,
20026
+ original: match,
20027
+ parameters: parseParameters(argumentsString),
20028
+ replacement: parseReplacement(replacement),
20029
+ type: "constructor",
20030
+ typeName
20031
+ };
20032
+ }
20033
+ const staticMethodMatch = STATIC_METHOD_PATTERN.exec(match);
20034
+ if (staticMethodMatch) {
20035
+ const { typeName, methodName, argumentsString } = staticMethodMatch.groups;
20036
+ return {
20037
+ conditions,
20038
+ methodName,
20039
+ original: match,
20040
+ parameters: parseParameters(argumentsString),
20041
+ replacement: parseReplacement(replacement),
20042
+ type: "staticMethod",
20043
+ typeName
20044
+ };
20045
+ }
20046
+ throw new Error(`Invalid pattern: ${match}`);
20047
+ }
20048
+ // src/utilities/pattern-replacement/pattern-types.ts
20049
+ function pattern4(configuration) {
20050
+ return configuration;
20051
+ }
20052
+ // src/utilities/pattern-replacement/replacement-generator.ts
20053
+ function getReplacementIdentifier(replacement) {
20054
+ switch (replacement.kind) {
20055
+ case "identifier":
20056
+ return replacement.name;
20057
+ case "call":
20058
+ return replacement.name;
20059
+ case "staticAccess":
20060
+ return;
20061
+ }
20062
+ }
20063
+ function generateReplacement(replacement, captures) {
20064
+ const { kind } = replacement;
20065
+ switch (kind) {
20066
+ case "identifier":
20067
+ return replacement.name;
20068
+ case "staticAccess":
20069
+ return `${replacement.typeName}.${replacement.property}`;
20070
+ case "call": {
20071
+ const parameters3 = replacement.parameters.map((argument) => {
20072
+ if (argument.startsWith("$")) {
20073
+ const captureName = argument.slice(1);
20074
+ const captured = captures.get(captureName);
20075
+ if (captured === undefined)
20076
+ throw new Error(`Missing capture: ${captureName}`);
20077
+ return captured.sourceText;
20078
+ }
20079
+ return argument;
20080
+ });
20081
+ return `${replacement.name}(${parameters3.join(", ")})`;
20082
+ }
20083
+ default:
20084
+ throw new Error(`Unknown replacement kind: ${kind}`);
20085
+ }
20086
+ }
20087
+ // src/rules/prefer-pattern-replacements.ts
20088
+ var isRuleOptions2 = Compile(build_default.Object({
20089
+ patterns: build_default.Array(build_default.Object({}, { additionalProperties: true }))
20090
+ }));
20091
+ function parsePatterns(patterns2) {
20092
+ return patterns2.map((pattern5) => parsePattern(pattern5.match, pattern5.replacement, pattern5.when));
20093
+ }
20094
+ var preferPatternReplacements = {
20095
+ create(context) {
20096
+ const validatedOptions = isRuleOptions2.Check(context.options[0]) ? context.options[0] : undefined;
20097
+ if (!validatedOptions || validatedOptions.patterns.length === 0)
20098
+ return {};
20099
+ const parsedPatterns = parsePatterns(validatedOptions.patterns);
20100
+ const patternIndex = buildPatternIndex(parsedPatterns);
20101
+ const { sourceCode } = context;
20102
+ function hasNameConflict(node, identifierName) {
20103
+ let scope = sourceCode.getScope(node);
20104
+ while (scope) {
20105
+ if (scope.set.has(identifierName))
20106
+ return true;
20107
+ scope = scope.upper ?? undefined;
20108
+ }
20109
+ return false;
20110
+ }
20111
+ function checkNode(node) {
20112
+ const resolved = resolveCallee(node);
20113
+ if (resolved.kind === "unknown")
20114
+ return;
20115
+ const key = resolved.kind === "constructor" ? `constructor:${resolved.typeName}` : `staticMethod:${resolved.typeName}:${resolved.methodName}`;
20116
+ const candidates2 = patternIndex.get(key);
20117
+ if (!candidates2 || candidates2.length === 0)
20118
+ return;
20119
+ for (const pattern5 of candidates2) {
20120
+ const captures = matchParameters(pattern5.parameters, node.arguments, sourceCode);
20121
+ if (!(captures && evaluateConditions(pattern5.conditions, captures) && canSafelySubstitute(captures))) {
20122
+ continue;
20123
+ }
20124
+ const replacementId = getReplacementIdentifier(pattern5.replacement);
20125
+ if (replacementId && hasNameConflict(node, replacementId))
20126
+ continue;
20127
+ const originalText = sourceCode.getText(node);
20128
+ const replacementText = generateReplacement(pattern5.replacement, captures);
20129
+ context.report({
20130
+ data: { original: originalText, replacement: replacementText },
20131
+ fix: (fixer) => fixer.replaceText(node, replacementText),
20132
+ messageId: "preferReplacement",
20133
+ node
20134
+ });
20135
+ return;
20136
+ }
20137
+ }
20138
+ return {
20139
+ CallExpression: checkNode,
20140
+ NewExpression: checkNode
20141
+ };
20142
+ },
20143
+ defaultOptions: [{ patterns: [] }],
20144
+ meta: {
20145
+ docs: {
20146
+ description: "Enforce using configured replacements for common constructor/method patterns",
20147
+ recommended: false
20148
+ },
20149
+ fixable: "code",
20150
+ messages: {
20151
+ preferReplacement: "Prefer '{{replacement}}' over '{{original}}'"
20152
+ },
20153
+ schema: [
20154
+ {
20155
+ additionalProperties: false,
20156
+ properties: {
20157
+ patterns: {
20158
+ items: { type: "object" },
20159
+ type: "array"
20160
+ }
20161
+ },
20162
+ type: "object"
20163
+ }
20164
+ ],
20165
+ type: "suggestion"
20166
+ }
20167
+ };
20168
+ var prefer_pattern_replacements_default = preferPatternReplacements;
20169
+
20170
+ // src/rules/prefer-sequence-overloads.ts
20171
+ import { AST_NODE_TYPES as AST_NODE_TYPES11 } from "@typescript-eslint/types";
19641
20172
  var sequenceDescriptors = [
19642
20173
  { keypointName: "ColorSequenceKeypoint", sequenceName: "ColorSequence" },
19643
20174
  { keypointName: "NumberSequenceKeypoint", sequenceName: "NumberSequence" }
19644
20175
  ];
19645
20176
  function isSequenceIdentifier(node) {
19646
- if (node.type !== AST_NODE_TYPES9.Identifier)
20177
+ if (node.type !== AST_NODE_TYPES11.Identifier)
19647
20178
  return false;
19648
20179
  for (const { sequenceName } of sequenceDescriptors)
19649
20180
  if (sequenceName === node.name)
@@ -19657,16 +20188,16 @@ function findDescriptor(sequenceName) {
19657
20188
  return;
19658
20189
  }
19659
20190
  var isNumericLiteral = Compile(build_default.Object({
19660
- type: build_default.Literal(AST_NODE_TYPES9.Literal),
20191
+ type: build_default.Literal(AST_NODE_TYPES11.Literal),
19661
20192
  value: build_default.Number()
19662
20193
  }));
19663
20194
  function isExpressionArgument(argument) {
19664
- return argument !== undefined && argument.type !== AST_NODE_TYPES9.SpreadElement;
20195
+ return argument !== undefined && argument.type !== AST_NODE_TYPES11.SpreadElement;
19665
20196
  }
19666
20197
  function extractKeypoint(element, descriptor) {
19667
- if (element === undefined || element.type !== AST_NODE_TYPES9.NewExpression)
20198
+ if (element === undefined || element.type !== AST_NODE_TYPES11.NewExpression)
19668
20199
  return;
19669
- if (element.callee.type !== AST_NODE_TYPES9.Identifier || element.callee.name !== descriptor.keypointName)
20200
+ if (element.callee.type !== AST_NODE_TYPES11.Identifier || element.callee.name !== descriptor.keypointName)
19670
20201
  return;
19671
20202
  if (element.arguments.length !== 2)
19672
20203
  return;
@@ -19696,7 +20227,7 @@ var preferSequenceOverloads = {
19696
20227
  if (descriptor === undefined || node.arguments.length !== 1)
19697
20228
  return;
19698
20229
  const [argument] = node.arguments;
19699
- if (argument === undefined || argument.type !== AST_NODE_TYPES9.ArrayExpression || argument.elements.length !== 2)
20230
+ if (argument === undefined || argument.type !== AST_NODE_TYPES11.ArrayExpression || argument.elements.length !== 2)
19700
20231
  return;
19701
20232
  const firstElement = argument.elements[0] ?? undefined;
19702
20233
  const secondElement = argument.elements[1] ?? undefined;
@@ -20042,16 +20573,16 @@ var preferUDim2Shorthand = {
20042
20573
  var prefer_udim2_shorthand_default = preferUDim2Shorthand;
20043
20574
 
20044
20575
  // src/rules/react-hooks-strict-return.ts
20045
- import { AST_NODE_TYPES as AST_NODE_TYPES10 } from "@typescript-eslint/utils";
20576
+ import { AST_NODE_TYPES as AST_NODE_TYPES12 } from "@typescript-eslint/utils";
20046
20577
  var MAX_RETURN_ELEMENTS = 2;
20047
20578
  var HOOK_PATTERN = /^use[A-Z0-9].*$/;
20048
20579
  function isHookNode(node) {
20049
20580
  let name;
20050
- if (node.type === AST_NODE_TYPES10.VariableDeclarator && node.id.type === AST_NODE_TYPES10.Identifier) {
20581
+ if (node.type === AST_NODE_TYPES12.VariableDeclarator && node.id.type === AST_NODE_TYPES12.Identifier) {
20051
20582
  ({ name } = node.id);
20052
- } else if (node.type === AST_NODE_TYPES10.FunctionDeclaration && node.id)
20583
+ } else if (node.type === AST_NODE_TYPES12.FunctionDeclaration && node.id)
20053
20584
  ({ name } = node.id);
20054
- else if (node.type === AST_NODE_TYPES10.FunctionExpression && node.id)
20585
+ else if (node.type === AST_NODE_TYPES12.FunctionExpression && node.id)
20055
20586
  ({ name } = node.id);
20056
20587
  return name !== undefined && HOOK_PATTERN.test(name);
20057
20588
  }
@@ -20075,27 +20606,27 @@ function getArrayElementsFromVariable(scope, name) {
20075
20606
  if (!identifier3.parent)
20076
20607
  continue;
20077
20608
  const { parent } = identifier3;
20078
- if (parent.type !== AST_NODE_TYPES10.VariableDeclarator)
20609
+ if (parent.type !== AST_NODE_TYPES12.VariableDeclarator)
20079
20610
  continue;
20080
- if (!parent.init || parent.init.type !== AST_NODE_TYPES10.ArrayExpression)
20611
+ if (!parent.init || parent.init.type !== AST_NODE_TYPES12.ArrayExpression)
20081
20612
  continue;
20082
20613
  elements.push(...parent.init.elements);
20083
20614
  }
20084
20615
  return elements;
20085
20616
  }
20086
20617
  function countReturnElements(argument, scope) {
20087
- if (argument.type === AST_NODE_TYPES10.Identifier)
20618
+ if (argument.type === AST_NODE_TYPES12.Identifier)
20088
20619
  return getArrayElementsFromVariable(scope, argument.name).length;
20089
- if (argument.type !== AST_NODE_TYPES10.ArrayExpression)
20620
+ if (argument.type !== AST_NODE_TYPES12.ArrayExpression)
20090
20621
  return 0;
20091
20622
  let count = 0;
20092
20623
  for (const element of argument.elements) {
20093
20624
  if (element === null)
20094
20625
  count += 1;
20095
- else if (element.type === AST_NODE_TYPES10.SpreadElement) {
20096
- if (element.argument.type === AST_NODE_TYPES10.Identifier) {
20626
+ else if (element.type === AST_NODE_TYPES12.SpreadElement) {
20627
+ if (element.argument.type === AST_NODE_TYPES12.Identifier) {
20097
20628
  count += getArrayElementsFromVariable(scope, element.argument.name).length;
20098
- } else if (element.argument.type === AST_NODE_TYPES10.ArrayExpression) {
20629
+ } else if (element.argument.type === AST_NODE_TYPES12.ArrayExpression) {
20099
20630
  count += element.argument.elements.length;
20100
20631
  } else
20101
20632
  count += 1;
@@ -20108,14 +20639,14 @@ function exceedsMaxReturnProperties(node, scope) {
20108
20639
  const { argument } = node;
20109
20640
  if (argument === null)
20110
20641
  return false;
20111
- if (argument.type === AST_NODE_TYPES10.ObjectExpression)
20642
+ if (argument.type === AST_NODE_TYPES12.ObjectExpression)
20112
20643
  return false;
20113
- if (argument.type === AST_NODE_TYPES10.Identifier) {
20644
+ if (argument.type === AST_NODE_TYPES12.Identifier) {
20114
20645
  const variable = getVariableByName(scope, argument.name);
20115
20646
  if (variable) {
20116
20647
  for (const { identifier: identifier3 } of variable.references) {
20117
20648
  const { parent } = identifier3;
20118
- if (parent?.type === AST_NODE_TYPES10.VariableDeclarator && parent.init?.type === AST_NODE_TYPES10.ObjectExpression) {
20649
+ if (parent?.type === AST_NODE_TYPES12.VariableDeclarator && parent.init?.type === AST_NODE_TYPES12.ObjectExpression) {
20119
20650
  return false;
20120
20651
  }
20121
20652
  }
@@ -20137,13 +20668,13 @@ var reactHooksStrictReturn = {
20137
20668
  return {
20138
20669
  ArrowFunctionExpression(node) {
20139
20670
  const { parent } = node;
20140
- if (parent?.type === AST_NODE_TYPES10.VariableDeclarator && parent.id.type === AST_NODE_TYPES10.Identifier && HOOK_PATTERN.test(parent.id.name)) {
20671
+ if (parent?.type === AST_NODE_TYPES12.VariableDeclarator && parent.id.type === AST_NODE_TYPES12.Identifier && HOOK_PATTERN.test(parent.id.name)) {
20141
20672
  hookDepth += 1;
20142
20673
  }
20143
20674
  },
20144
20675
  "ArrowFunctionExpression:exit"(node) {
20145
20676
  const { parent } = node;
20146
- if (parent?.type === AST_NODE_TYPES10.VariableDeclarator && parent.id.type === AST_NODE_TYPES10.Identifier && HOOK_PATTERN.test(parent.id.name)) {
20677
+ if (parent?.type === AST_NODE_TYPES12.VariableDeclarator && parent.id.type === AST_NODE_TYPES12.Identifier && HOOK_PATTERN.test(parent.id.name)) {
20147
20678
  hookDepth -= 1;
20148
20679
  }
20149
20680
  },
@@ -20192,7 +20723,7 @@ var isHookConfiguration = build_default.Object({
20192
20723
  allowAsync: build_default.Boolean(),
20193
20724
  name: build_default.String()
20194
20725
  });
20195
- var isRuleOptions2 = Compile(build_default.Object({
20726
+ var isRuleOptions3 = Compile(build_default.Object({
20196
20727
  environment: isEnvironmentMode,
20197
20728
  hooks: build_default.Array(isHookConfiguration)
20198
20729
  }, { additionalProperties: true }));
@@ -20203,7 +20734,7 @@ function parseOptions2(options3) {
20203
20734
  hooks: DEFAULT_HOOKS
20204
20735
  };
20205
20736
  }
20206
- if (!isRuleOptions2.Check(options3)) {
20737
+ if (!isRuleOptions3.Check(options3)) {
20207
20738
  return {
20208
20739
  environment: "roblox-ts",
20209
20740
  hooks: DEFAULT_HOOKS
@@ -20513,7 +21044,7 @@ var requireNamedEffectFunctions = {
20513
21044
  var require_named_effect_functions_default = requireNamedEffectFunctions;
20514
21045
 
20515
21046
  // src/rules/require-paired-calls.ts
20516
- import { AST_NODE_TYPES as AST_NODE_TYPES11 } from "@typescript-eslint/types";
21047
+ import { AST_NODE_TYPES as AST_NODE_TYPES13 } from "@typescript-eslint/types";
20517
21048
  var isStringArray = Compile(build_default.Readonly(build_default.Array(build_default.String())));
20518
21049
  var isPairConfiguration = Compile(build_default.Readonly(build_default.Object({
20519
21050
  alternatives: build_default.Optional(isStringArray),
@@ -20524,27 +21055,27 @@ var isPairConfiguration = Compile(build_default.Readonly(build_default.Object({
20524
21055
  requireSync: build_default.Optional(build_default.Boolean()),
20525
21056
  yieldingFunctions: build_default.Optional(isStringArray)
20526
21057
  })));
20527
- var isRuleOptions3 = Compile(build_default.Partial(build_default.Readonly(build_default.Object({
21058
+ var isRuleOptions4 = Compile(build_default.Partial(build_default.Readonly(build_default.Object({
20528
21059
  allowConditionalClosers: build_default.Optional(build_default.Boolean()),
20529
21060
  allowMultipleOpeners: build_default.Optional(build_default.Boolean()),
20530
21061
  maxNestingDepth: build_default.Optional(build_default.Number()),
20531
21062
  pairs: build_default.Readonly(build_default.Array(isPairConfiguration))
20532
21063
  }))));
20533
21064
  var LOOP_NODE_TYPES = new Set([
20534
- AST_NODE_TYPES11.DoWhileStatement,
20535
- AST_NODE_TYPES11.ForInStatement,
20536
- AST_NODE_TYPES11.ForOfStatement,
20537
- AST_NODE_TYPES11.ForStatement,
20538
- AST_NODE_TYPES11.WhileStatement
21065
+ AST_NODE_TYPES13.DoWhileStatement,
21066
+ AST_NODE_TYPES13.ForInStatement,
21067
+ AST_NODE_TYPES13.ForOfStatement,
21068
+ AST_NODE_TYPES13.ForStatement,
21069
+ AST_NODE_TYPES13.WhileStatement
20539
21070
  ]);
20540
21071
  var DEFAULT_ROBLOX_YIELDING_FUNCTIONS = ["task.wait", "wait", "*.WaitForChild", "*.*Async"];
20541
21072
  function getCallName(node) {
20542
21073
  const { callee } = node;
20543
- if (callee.type === AST_NODE_TYPES11.Identifier)
21074
+ if (callee.type === AST_NODE_TYPES13.Identifier)
20544
21075
  return callee.name;
20545
- if (callee.type === AST_NODE_TYPES11.MemberExpression) {
20546
- const object3 = callee.object.type === AST_NODE_TYPES11.Identifier ? callee.object.name : undefined;
20547
- const property = callee.property.type === AST_NODE_TYPES11.Identifier ? callee.property.name : undefined;
21076
+ if (callee.type === AST_NODE_TYPES13.MemberExpression) {
21077
+ const object3 = callee.object.type === AST_NODE_TYPES13.Identifier ? callee.object.name : undefined;
21078
+ const property = callee.property.type === AST_NODE_TYPES13.Identifier ? callee.property.name : undefined;
20548
21079
  if (object3 !== undefined && property !== undefined)
20549
21080
  return `${object3}.${property}`;
20550
21081
  }
@@ -20580,12 +21111,12 @@ function isLoopLikeStatement(node) {
20580
21111
  return LOOP_NODE_TYPES.has(node.type);
20581
21112
  }
20582
21113
  function isSwitchStatement(node) {
20583
- return node?.type === AST_NODE_TYPES11.SwitchStatement;
21114
+ return node?.type === AST_NODE_TYPES13.SwitchStatement;
20584
21115
  }
20585
21116
  function findLabeledStatementBody(label, startingNode) {
20586
21117
  let current = startingNode;
20587
21118
  while (current) {
20588
- if (current.type === AST_NODE_TYPES11.LabeledStatement && current.label.name === label.name)
21119
+ if (current.type === AST_NODE_TYPES13.LabeledStatement && current.label.name === label.name)
20589
21120
  return current.body;
20590
21121
  current = current.parent ?? undefined;
20591
21122
  }
@@ -20620,10 +21151,21 @@ function resolveContinueTargetLoop(statement) {
20620
21151
  function cloneEntry(value) {
20621
21152
  return { ...value, loopAncestors: [...value.loopAncestors] };
20622
21153
  }
21154
+ var messages = {
21155
+ asyncViolation: "Cannot use {{asyncType}} between '{{opener}}' and '{{closer}}' (requireSync: true)",
21156
+ conditionalOpener: "Conditional opener '{{opener}}' at {{location}} may not have matching closer on all paths",
21157
+ maxNestingExceeded: "Maximum nesting depth of {{max}} exceeded for paired calls",
21158
+ multipleOpeners: "Multiple consecutive calls to '{{opener}}' without matching closers (allowMultipleOpeners: false)",
21159
+ robloxYieldViolation: "Yielding function '{{yieldingFunction}}' auto-closes all profiles - subsequent '{{closer}}' will error",
21160
+ unexpectedCloser: "Unexpected call to '{{closer}}' - expected one of: {{expected}}",
21161
+ unpairedCloser: "Unexpected call to '{{closer}}' - no matching opener on stack",
21162
+ unpairedOpener: "Unpaired call to '{{opener}}' - missing '{{closer}}' on {{paths}}",
21163
+ wrongOrder: "Closer '{{closer}}' called out of order - expected to close '{{expected}}' but '{{actual}}' is still open"
21164
+ };
20623
21165
  var rule = {
20624
21166
  create(context) {
20625
21167
  const [rawOptions] = context.options;
20626
- const baseOptions = isRuleOptions3.Check(rawOptions) ? rawOptions : {};
21168
+ const baseOptions = isRuleOptions4.Check(rawOptions) ? rawOptions : {};
20627
21169
  const options3 = {
20628
21170
  allowConditionalClosers: baseOptions.allowConditionalClosers ?? false,
20629
21171
  allowMultipleOpeners: baseOptions.allowMultipleOpeners ?? true,
@@ -20656,12 +21198,14 @@ var rule = {
20656
21198
  if (closerToOpenersCache.has(closer))
20657
21199
  return closerToOpenersCache.get(closer) ?? [];
20658
21200
  const names = new Array;
21201
+ let size = 0;
20659
21202
  for (const pair of options3.pairs) {
20660
21203
  if (!getValidClosers(pair).includes(closer))
20661
21204
  continue;
20662
- for (const openerName of getAllOpeners(pair))
21205
+ for (const openerName of getAllOpeners(pair)) {
20663
21206
  if (!names.includes(openerName))
20664
- names.push(openerName);
21207
+ names[size++] = openerName;
21208
+ }
20665
21209
  }
20666
21210
  closerToOpenersCache.set(closer, names);
20667
21211
  return names;
@@ -20670,6 +21214,7 @@ var rule = {
20670
21214
  if (openerToClosersCache.has(opener))
20671
21215
  return openerToClosersCache.get(opener) ?? [];
20672
21216
  const closers = new Array;
21217
+ let size = 0;
20673
21218
  for (const pair of options3.pairs) {
20674
21219
  const allOpeners = getAllOpeners(pair);
20675
21220
  if (!allOpeners.includes(opener))
@@ -20677,7 +21222,7 @@ var rule = {
20677
21222
  const validClosers = getValidClosers(pair);
20678
21223
  for (const closer of validClosers)
20679
21224
  if (!closers.includes(closer))
20680
- closers.push(closer);
21225
+ closers[size++] = closer;
20681
21226
  }
20682
21227
  openerToClosersCache.set(opener, closers);
20683
21228
  return closers;
@@ -20713,24 +21258,19 @@ var rule = {
20713
21258
  function saveSnapshot(node) {
20714
21259
  stackSnapshots.set(node, cloneStack());
20715
21260
  }
20716
- function findPairConfig(functionName, isOpener) {
20717
- return options3.pairs.find((pair) => {
20718
- if (isOpener)
20719
- return getAllOpeners(pair).includes(functionName);
20720
- const validClosers = getValidClosers(pair);
20721
- return validClosers.includes(functionName);
20722
- });
21261
+ function findPairConfiguration(functionName, isOpener) {
21262
+ return options3.pairs.find((pair) => (isOpener ? getAllOpeners(pair) : getValidClosers(pair)).includes(functionName));
20723
21263
  }
20724
21264
  function isRobloxYieldingFunction(functionName, configuration) {
20725
21265
  if (configuration.platform !== "roblox")
20726
21266
  return false;
20727
21267
  const yieldingFunctions = configuration.yieldingFunctions ?? DEFAULT_ROBLOX_YIELDING_FUNCTIONS;
20728
- return yieldingFunctions.some((pattern4) => {
20729
- if (pattern4.startsWith("*.")) {
20730
- const methodName = pattern4.slice(2);
21268
+ return yieldingFunctions.some((pattern5) => {
21269
+ if (pattern5.startsWith("*.")) {
21270
+ const methodName = pattern5.slice(2);
20731
21271
  return functionName.endsWith(`.${methodName}`);
20732
21272
  }
20733
- return functionName === pattern4;
21273
+ return functionName === pattern5;
20734
21274
  });
20735
21275
  }
20736
21276
  function onFunctionEnter(node) {
@@ -20838,7 +21378,7 @@ var rule = {
20838
21378
  function onIfConsequentExit(node) {
20839
21379
  const consequentNode = node;
20840
21380
  const { parent } = consequentNode;
20841
- if (parent?.type === AST_NODE_TYPES11.IfStatement) {
21381
+ if (parent?.type === AST_NODE_TYPES13.IfStatement) {
20842
21382
  const branches = branchStacks.get(parent) ?? [];
20843
21383
  branches.push(cloneStack());
20844
21384
  branchStacks.set(parent, branches);
@@ -20853,7 +21393,7 @@ var rule = {
20853
21393
  function onIfAlternateExit(node) {
20854
21394
  const alternateNode = node;
20855
21395
  const { parent } = alternateNode;
20856
- if (parent?.type === AST_NODE_TYPES11.IfStatement) {
21396
+ if (parent?.type === AST_NODE_TYPES13.IfStatement) {
20857
21397
  const branches = branchStacks.get(parent) ?? [];
20858
21398
  branches.push(cloneStack());
20859
21399
  branchStacks.set(parent, branches);
@@ -20902,7 +21442,7 @@ var rule = {
20902
21442
  function onTryBlockExit(node) {
20903
21443
  const blockNode = node;
20904
21444
  const { parent } = blockNode;
20905
- if (parent?.type === AST_NODE_TYPES11.TryStatement) {
21445
+ if (parent?.type === AST_NODE_TYPES13.TryStatement) {
20906
21446
  const branches = branchStacks.get(parent) ?? [];
20907
21447
  branches.push(cloneStack());
20908
21448
  branchStacks.set(parent, branches);
@@ -20921,7 +21461,7 @@ var rule = {
20921
21461
  function onCatchClauseExit(node) {
20922
21462
  const catchNode = node;
20923
21463
  const { parent } = catchNode;
20924
- if (parent?.type === AST_NODE_TYPES11.TryStatement) {
21464
+ if (parent?.type === AST_NODE_TYPES13.TryStatement) {
20925
21465
  const branches = branchStacks.get(parent) ?? [];
20926
21466
  branches.push(cloneStack());
20927
21467
  branchStacks.set(parent, branches);
@@ -20984,7 +21524,7 @@ var rule = {
20984
21524
  function onSwitchCaseExit(node) {
20985
21525
  const caseNode = node;
20986
21526
  const { parent } = caseNode;
20987
- if (parent?.type === AST_NODE_TYPES11.SwitchStatement) {
21527
+ if (parent?.type === AST_NODE_TYPES13.SwitchStatement) {
20988
21528
  const branches = branchStacks.get(parent) ?? [];
20989
21529
  branches.push(cloneStack());
20990
21530
  branchStacks.set(parent, branches);
@@ -21015,7 +21555,7 @@ var rule = {
21015
21555
  for (const { opener, config, node: node2 } of openerStack) {
21016
21556
  const validClosers = getValidClosers(config);
21017
21557
  const closer = validClosers.length === 1 ? validClosers[0] ?? "closer" : validClosers.join("' or '");
21018
- const statementType = statementNode.type === AST_NODE_TYPES11.ReturnStatement ? "return" : "throw";
21558
+ const statementType = statementNode.type === AST_NODE_TYPES13.ReturnStatement ? "return" : "throw";
21019
21559
  const lineNumber = statementNode.loc?.start.line ?? 0;
21020
21560
  context.report({
21021
21561
  data: {
@@ -21032,7 +21572,7 @@ var rule = {
21032
21572
  const statementNode = node;
21033
21573
  if (openerStack.length === 0)
21034
21574
  return;
21035
- const targetLoop = statementNode.type === AST_NODE_TYPES11.ContinueStatement ? resolveContinueTargetLoop(statementNode) : resolveBreakTargetLoop(statementNode);
21575
+ const targetLoop = statementNode.type === AST_NODE_TYPES13.ContinueStatement ? resolveContinueTargetLoop(statementNode) : resolveBreakTargetLoop(statementNode);
21036
21576
  if (!targetLoop)
21037
21577
  return;
21038
21578
  for (const { node: openerNode, config, opener, loopAncestors } of openerStack) {
@@ -21040,7 +21580,7 @@ var rule = {
21040
21580
  continue;
21041
21581
  const validClosers = getValidClosers(config);
21042
21582
  const closer = validClosers.length === 1 ? validClosers[0] ?? "closer" : validClosers.join("' or '");
21043
- const statementType = statementNode.type === AST_NODE_TYPES11.BreakStatement ? "break" : "continue";
21583
+ const statementType = statementNode.type === AST_NODE_TYPES13.BreakStatement ? "break" : "continue";
21044
21584
  const lineNumber = statementNode.loc?.start.line ?? 0;
21045
21585
  context.report({
21046
21586
  data: {
@@ -21058,12 +21598,12 @@ var rule = {
21058
21598
  const callName = getCallName(callNode);
21059
21599
  if (callName === undefined || callName === "")
21060
21600
  return;
21061
- const openerConfig = findPairConfig(callName, true);
21601
+ const openerConfig = findPairConfiguration(callName, true);
21062
21602
  if (openerConfig) {
21063
21603
  handleOpener(callNode, callName, openerConfig);
21064
21604
  return;
21065
21605
  }
21066
- if (findPairConfig(callName, false)) {
21606
+ if (findPairConfiguration(callName, false)) {
21067
21607
  handleCloser(callNode, callName);
21068
21608
  return;
21069
21609
  }
@@ -21115,31 +21655,31 @@ var rule = {
21115
21655
  messageId: "unpairedCloser",
21116
21656
  node
21117
21657
  });
21658
+ return;
21659
+ }
21660
+ const topEntry = openerStack.at(-1);
21661
+ if (topEntry) {
21662
+ const expectedClosers = getExpectedClosersForOpener(topEntry.opener);
21663
+ const closerDescription = formatOpenerList(expectedClosers);
21664
+ context.report({
21665
+ data: {
21666
+ closer,
21667
+ expected: closerDescription
21668
+ },
21669
+ messageId: "unexpectedCloser",
21670
+ node
21671
+ });
21118
21672
  } else {
21119
- const topEntry = openerStack.at(-1);
21120
- if (topEntry) {
21121
- const expectedClosers = getExpectedClosersForOpener(topEntry.opener);
21122
- const closerDescription = formatOpenerList(expectedClosers);
21123
- context.report({
21124
- data: {
21125
- closer,
21126
- expected: closerDescription
21127
- },
21128
- messageId: "unexpectedCloser",
21129
- node
21130
- });
21131
- } else {
21132
- const openerCandidates = getConfiguredOpenersForCloser(closer);
21133
- const openerDescription = formatOpenerList(openerCandidates);
21134
- context.report({
21135
- data: {
21136
- closer,
21137
- opener: openerDescription
21138
- },
21139
- messageId: "unpairedCloser",
21140
- node
21141
- });
21142
- }
21673
+ const openerCandidates = getConfiguredOpenersForCloser(closer);
21674
+ const openerDescription = formatOpenerList(openerCandidates);
21675
+ context.report({
21676
+ data: {
21677
+ closer,
21678
+ opener: openerDescription
21679
+ },
21680
+ messageId: "unpairedCloser",
21681
+ node
21682
+ });
21143
21683
  }
21144
21684
  return;
21145
21685
  }
@@ -21178,7 +21718,7 @@ var rule = {
21178
21718
  continue;
21179
21719
  const validClosers = getValidClosers(config);
21180
21720
  const closer = validClosers.length === 1 ? validClosers[0] ?? "closer" : validClosers.join("' or '");
21181
- const asyncType = asyncNode.type === AST_NODE_TYPES11.AwaitExpression ? "await" : "yield";
21721
+ const asyncType = asyncNode.type === AST_NODE_TYPES13.AwaitExpression ? "await" : "yield";
21182
21722
  context.report({
21183
21723
  data: { asyncType, closer, opener },
21184
21724
  messageId: "asyncViolation",
@@ -21200,10 +21740,9 @@ var rule = {
21200
21740
  ForInStatement: onLoopEnter,
21201
21741
  "ForInStatement:exit": onLoopExit,
21202
21742
  ForOfStatement: (node) => {
21203
- const forOfNode = node;
21204
- if (forOfNode.await)
21205
- onAsyncYield(forOfNode);
21206
- onLoopEnter(forOfNode);
21743
+ if (node.await)
21744
+ onAsyncYield(node);
21745
+ onLoopEnter(node);
21207
21746
  },
21208
21747
  "ForOfStatement:exit": onLoopExit,
21209
21748
  ForStatement: onLoopEnter,
@@ -21232,6 +21771,7 @@ var rule = {
21232
21771
  YieldExpression: onAsyncYield
21233
21772
  };
21234
21773
  },
21774
+ defaultOptions: [],
21235
21775
  meta: {
21236
21776
  docs: {
21237
21777
  description: "Enforces balanced opener/closer function calls across all execution paths",
@@ -21239,17 +21779,7 @@ var rule = {
21239
21779
  url: "https://github.com/howmanysmall/eslint-idiot-lint/tree/main/docs/rules/require-paired-calls.md"
21240
21780
  },
21241
21781
  fixable: "code",
21242
- messages: {
21243
- asyncViolation: "Cannot use {{asyncType}} between '{{opener}}' and '{{closer}}' (requireSync: true)",
21244
- conditionalOpener: "Conditional opener '{{opener}}' at {{location}} may not have matching closer on all paths",
21245
- maxNestingExceeded: "Maximum nesting depth of {{max}} exceeded for paired calls",
21246
- multipleOpeners: "Multiple consecutive calls to '{{opener}}' without matching closers (allowMultipleOpeners: false)",
21247
- robloxYieldViolation: "Yielding function '{{yieldingFunction}}' auto-closes all profiles - subsequent '{{closer}}' will error",
21248
- unexpectedCloser: "Unexpected call to '{{closer}}' - expected one of: {{expected}}",
21249
- unpairedCloser: "Unexpected call to '{{closer}}' - no matching opener on stack",
21250
- unpairedOpener: "Unpaired call to '{{opener}}' - missing '{{closer}}' on {{paths}}",
21251
- wrongOrder: "Closer '{{closer}}' called out of order - expected to close '{{expected}}' but '{{actual}}' is still open"
21252
- },
21782
+ messages,
21253
21783
  schema: [
21254
21784
  {
21255
21785
  additionalProperties: false,
@@ -21719,8 +22249,8 @@ function resolveRelativeImport(importSource, sourceFile) {
21719
22249
  }
21720
22250
 
21721
22251
  // src/rules/strict-component-boundaries.ts
21722
- function toRegExp(pattern4) {
21723
- return new RegExp(pattern4, "i");
22252
+ function toRegExp(pattern5) {
22253
+ return new RegExp(pattern5, "i");
21724
22254
  }
21725
22255
  function pathSegmentsFromSource(source) {
21726
22256
  return source.split("/").filter((part) => !part.startsWith("."));
@@ -21999,8 +22529,9 @@ function isStableValue(variable, identifierName, stableHooks) {
21999
22529
  return true;
22000
22530
  if (type3 === "Variable" && node.type === TSESTree10.AST_NODE_TYPES.VariableDeclarator) {
22001
22531
  const { parent } = node;
22002
- if (!parent || parent.type !== TSESTree10.AST_NODE_TYPES.VariableDeclaration || parent.kind !== "const")
22532
+ if (!parent || parent.type !== TSESTree10.AST_NODE_TYPES.VariableDeclaration || parent.kind !== "const") {
22003
22533
  continue;
22534
+ }
22004
22535
  const init = node.init;
22005
22536
  if (init && isStableHookValue(init, node, identifierName, stableHooks))
22006
22537
  return true;
@@ -22575,8 +23106,9 @@ function isHookCall(node) {
22575
23106
  const { callee } = node;
22576
23107
  if (callee.type === TSESTree11.AST_NODE_TYPES.Identifier)
22577
23108
  return isReactHook(callee.name);
22578
- if (callee.type === TSESTree11.AST_NODE_TYPES.MemberExpression && callee.property.type === TSESTree11.AST_NODE_TYPES.Identifier)
23109
+ if (callee.type === TSESTree11.AST_NODE_TYPES.MemberExpression && callee.property.type === TSESTree11.AST_NODE_TYPES.Identifier) {
22579
23110
  return isReactHook(callee.property.name);
23111
+ }
22580
23112
  return false;
22581
23113
  }
22582
23114
  var FUNCTION_BOUNDARIES2 = new Set([
@@ -22610,9 +23142,7 @@ function isRecursiveCall(node, functionName) {
22610
23142
  if (!functionName)
22611
23143
  return false;
22612
23144
  const { callee } = node;
22613
- if (callee.type === "Identifier")
22614
- return callee.name === functionName;
22615
- return false;
23145
+ return callee.type === "Identifier" && callee.name === functionName;
22616
23146
  }
22617
23147
  var useHookAtTopLevel = {
22618
23148
  create(context) {
@@ -22913,7 +23443,7 @@ function createNoInstanceMethodsOptions(options3 = {}) {
22913
23443
  };
22914
23444
  }
22915
23445
  function createNoShorthandOptions(options3 = {}) {
22916
- return { allowPropertyAccess: [], shorthands: {}, ...options3 };
23446
+ return { allowPropertyAccess: [], ignoreShorthands: [], shorthands: {}, ...options3 };
22917
23447
  }
22918
23448
  function createEffectFunctionOptions(options3 = {}) {
22919
23449
  return { environment: "standard", hooks: [], ...options3 };
@@ -22976,6 +23506,9 @@ function createNoUselessUseSpringOptions(options3 = {}) {
22976
23506
  ...options3
22977
23507
  };
22978
23508
  }
23509
+ function createPreferPatternReplacementsOptions(patterns2 = []) {
23510
+ return { patterns: patterns2 };
23511
+ }
22979
23512
 
22980
23513
  // src/index.ts
22981
23514
  var rules = {
@@ -22997,6 +23530,7 @@ var rules = {
22997
23530
  "prefer-early-return": prefer_early_return_default,
22998
23531
  "prefer-module-scope-constants": prefer_module_scope_constants_default,
22999
23532
  "prefer-pascal-case-enums": prefer_pascal_case_enums_default,
23533
+ "prefer-pattern-replacements": prefer_pattern_replacements_default,
23000
23534
  "prefer-sequence-overloads": prefer_sequence_overloads_default,
23001
23535
  "prefer-singular-enums": prefer_singular_enums_default,
23002
23536
  "prefer-udim2-shorthand": prefer_udim2_shorthand_default,
@@ -23038,12 +23572,14 @@ var plugin = {
23038
23572
  };
23039
23573
  var src_default = plugin;
23040
23574
  export {
23575
+ pattern4 as pattern,
23041
23576
  defaultRobloxProfilePair,
23042
23577
  src_default as default,
23043
23578
  createUseHookAtTopLevelOptions,
23044
23579
  createUseExhaustiveDependenciesOptions,
23045
23580
  createRequirePairedCallsOptions,
23046
23581
  createReactKeysOptions,
23582
+ createPreferPatternReplacementsOptions,
23047
23583
  createPairConfiguration,
23048
23584
  createNoUselessUseSpringOptions,
23049
23585
  createNoShorthandOptions,
@@ -23055,4 +23591,4 @@ export {
23055
23591
  createBanInstancesOptions
23056
23592
  };
23057
23593
 
23058
- //# debugId=C1BEC810E1B754DD64756E2164756E21
23594
+ //# debugId=928BD58DAA13C57A64756E2164756E21