eslint-plugin-code-style 1.3.8 → 1.3.10

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 (2) hide show
  1. package/index.js +169 -24
  2. package/package.json +1 -1
package/index.js CHANGED
@@ -1836,6 +1836,47 @@ const functionNamingConvention = {
1836
1836
 
1837
1837
  const endsWithHandler = (name) => handlerRegex.test(name);
1838
1838
 
1839
+ // Check if a node contains JSX (making it a React component)
1840
+ const containsJsxHandler = (node) => {
1841
+ if (!node) return false;
1842
+
1843
+ if (node.type === "JSXElement" || node.type === "JSXFragment") return true;
1844
+
1845
+ if (node.type === "BlockStatement") {
1846
+ for (const statement of node.body) {
1847
+ if (statement.type === "ReturnStatement" && statement.argument) {
1848
+ if (containsJsxHandler(statement.argument)) return true;
1849
+ }
1850
+
1851
+ if (statement.type === "IfStatement") {
1852
+ if (containsJsxHandler(statement.consequent)) return true;
1853
+ if (statement.alternate && containsJsxHandler(statement.alternate)) return true;
1854
+ }
1855
+ }
1856
+ }
1857
+
1858
+ if (node.type === "ConditionalExpression") {
1859
+ return containsJsxHandler(node.consequent) || containsJsxHandler(node.alternate);
1860
+ }
1861
+
1862
+ if (node.type === "LogicalExpression") {
1863
+ return containsJsxHandler(node.left) || containsJsxHandler(node.right);
1864
+ }
1865
+
1866
+ if (node.type === "ParenthesizedExpression") {
1867
+ return containsJsxHandler(node.expression);
1868
+ }
1869
+
1870
+ return false;
1871
+ };
1872
+
1873
+ // Check if function is a React component (PascalCase + returns JSX)
1874
+ const isReactComponentHandler = (node, name) => {
1875
+ if (!name || !/^[A-Z]/.test(name)) return false;
1876
+
1877
+ return containsJsxHandler(node.body);
1878
+ };
1879
+
1839
1880
  const checkFunctionHandler = (node) => {
1840
1881
  let name = null;
1841
1882
 
@@ -1852,7 +1893,10 @@ const functionNamingConvention = {
1852
1893
  // Skip hooks
1853
1894
  if (/^use[A-Z]/.test(name)) return;
1854
1895
 
1855
- // Check PascalCase functions
1896
+ // Skip React components (PascalCase + returns JSX)
1897
+ if (isReactComponentHandler(node, name)) return;
1898
+
1899
+ // Check PascalCase functions that are NOT React components
1856
1900
  if (/^[A-Z]/.test(name)) {
1857
1901
  // If starts with a verb (case-insensitive), it should be camelCase
1858
1902
  if (startsWithVerbCaseInsensitiveHandler(name)) {
@@ -13846,6 +13890,7 @@ const reactCodeOrder = {
13846
13890
 
13847
13891
  const checkCodeOrderHandler = (node, isHook) => {
13848
13892
  const body = node.body;
13893
+ const sourceCode = context.sourceCode || context.getSourceCode();
13849
13894
 
13850
13895
  // Only check block statements (not implicit returns)
13851
13896
  if (body.type !== "BlockStatement") return;
@@ -13894,44 +13939,143 @@ const reactCodeOrder = {
13894
13939
  }
13895
13940
  }
13896
13941
 
13897
- // Filter to only relevant statements (skip comments, empty statements)
13898
- const relevantStatements = statements.filter((s) => s.type === "VariableDeclaration"
13942
+ // Categorizable statement types for ordering
13943
+ const isCategorizableStatement = (s) => s.type === "VariableDeclaration"
13899
13944
  || s.type === "FunctionDeclaration"
13900
13945
  || s.type === "ExpressionStatement"
13901
- || s.type === "ReturnStatement");
13946
+ || s.type === "ReturnStatement";
13902
13947
 
13903
- if (relevantStatements.length < 2) return;
13948
+ // Filter to only categorizable statements for order checking
13949
+ const categorizableStatements = statements.filter(isCategorizableStatement);
13904
13950
 
13905
- // Track the categories and their order
13951
+ if (categorizableStatements.length < 2) return;
13952
+
13953
+ // Check order violations using only categorizable statements
13954
+ let hasOrderViolation = false;
13906
13955
  let lastCategory = 0;
13907
- let lastCategoryStatement = null;
13956
+ let violatingStatement = null;
13957
+ let violatingCategory = null;
13958
+ let previousCategory = null;
13908
13959
 
13909
- for (const statement of relevantStatements) {
13960
+ for (const statement of categorizableStatements) {
13910
13961
  const category = getStatementCategoryHandler(statement, propNames);
13911
13962
 
13912
- // Skip unknown statements - they could be anything
13963
+ // Skip unknown statements for order checking
13913
13964
  if (category === ORDER.UNKNOWN) continue;
13914
13965
 
13915
13966
  // Check if current category comes before the last one
13916
- if (category < lastCategory) {
13917
- // Find what it should come after
13918
- context.report({
13919
- data: {
13920
- current: ORDER_NAMES[category],
13921
- previous: ORDER_NAMES[lastCategory],
13922
- type: isHook ? "hook" : "component",
13923
- },
13924
- message: "\"{{current}}\" should come before \"{{previous}}\" in {{type}}. Order: refs → state → redux → router → context → custom hooks → derived → useMemo → useCallback → handlers → useEffect → return",
13925
- node: statement,
13926
- });
13927
-
13928
- // Only report first violation to avoid noise
13929
- return;
13967
+ if (category < lastCategory && !hasOrderViolation) {
13968
+ hasOrderViolation = true;
13969
+ violatingStatement = statement;
13970
+ violatingCategory = category;
13971
+ previousCategory = lastCategory;
13930
13972
  }
13931
13973
 
13932
13974
  lastCategory = category;
13933
- lastCategoryStatement = statement;
13934
13975
  }
13976
+
13977
+ if (!hasOrderViolation) return;
13978
+
13979
+ // For auto-fix, process ALL statements and assign sort keys
13980
+ // Non-categorizable statements (if, for, while, etc.) get the category of the NEXT categorizable statement
13981
+ // This keeps them positioned just before whatever comes after them
13982
+ const allStatementsWithSortKeys = [];
13983
+
13984
+ // First pass: assign categories to categorizable statements
13985
+ const categoryMap = new Map();
13986
+
13987
+ for (const stmt of statements) {
13988
+ if (isCategorizableStatement(stmt)) {
13989
+ categoryMap.set(stmt, getStatementCategoryHandler(stmt, propNames));
13990
+ }
13991
+ }
13992
+
13993
+ // Second pass: assign sort keys to all statements
13994
+ // Non-categorizable statements get the category of the next categorizable statement
13995
+ for (let i = 0; i < statements.length; i++) {
13996
+ const stmt = statements[i];
13997
+ let sortKey;
13998
+
13999
+ if (isCategorizableStatement(stmt)) {
14000
+ sortKey = categoryMap.get(stmt);
14001
+ } else {
14002
+ // Find the next categorizable statement's category
14003
+ sortKey = ORDER.RETURN; // Default to RETURN if none found
14004
+
14005
+ for (let j = i + 1; j < statements.length; j++) {
14006
+ if (isCategorizableStatement(statements[j])) {
14007
+ sortKey = categoryMap.get(statements[j]);
14008
+
14009
+ break;
14010
+ }
14011
+ }
14012
+ }
14013
+
14014
+ allStatementsWithSortKeys.push({
14015
+ index: i,
14016
+ sortKey,
14017
+ statement: stmt,
14018
+ });
14019
+ }
14020
+
14021
+ // Sort all statements by sort key (stable sort - maintains relative order within same key)
14022
+ const sortedStatements = [...allStatementsWithSortKeys].sort((a, b) => {
14023
+ if (a.sortKey !== b.sortKey) {
14024
+ return a.sortKey - b.sortKey;
14025
+ }
14026
+
14027
+ // Maintain original order within the same sort key
14028
+ return a.index - b.index;
14029
+ });
14030
+
14031
+ // Check if sorting actually changes the order
14032
+ const orderChanged = sortedStatements.some((s, i) => s.index !== i);
14033
+
14034
+ if (!orderChanged) return;
14035
+
14036
+ // Build the fix
14037
+ const fixHandler = (fixer) => {
14038
+ // Get the base indentation from the first statement
14039
+ const firstStatementLine = sourceCode.lines[statements[0].loc.start.line - 1];
14040
+ const baseIndent = firstStatementLine.match(/^\s*/)[0];
14041
+
14042
+ // Build new body content
14043
+ let newBodyContent = "";
14044
+ let lastSortKey = null;
14045
+
14046
+ for (let i = 0; i < sortedStatements.length; i++) {
14047
+ const { sortKey, statement } = sortedStatements[i];
14048
+
14049
+ // Add blank line between different categories (except UNKNOWN)
14050
+ if (lastSortKey !== null && sortKey !== ORDER.UNKNOWN && lastSortKey !== ORDER.UNKNOWN && sortKey !== lastSortKey) {
14051
+ newBodyContent += "\n";
14052
+ }
14053
+
14054
+ // Get the statement text with proper indentation
14055
+ const stmtText = sourceCode.getText(statement);
14056
+
14057
+ newBodyContent += baseIndent + stmtText.trim() + "\n";
14058
+
14059
+ lastSortKey = sortKey;
14060
+ }
14061
+
14062
+ // Find the range to replace (all statements)
14063
+ const firstStmt = statements[0];
14064
+ const lastStmt = statements[statements.length - 1];
14065
+
14066
+ return fixer.replaceTextRange([firstStmt.range[0], lastStmt.range[1]], newBodyContent.trimEnd());
14067
+ };
14068
+
14069
+ context.report({
14070
+ data: {
14071
+ current: ORDER_NAMES[violatingCategory],
14072
+ previous: ORDER_NAMES[previousCategory],
14073
+ type: isHook ? "hook" : "component",
14074
+ },
14075
+ fix: fixHandler,
14076
+ message: "\"{{current}}\" should come before \"{{previous}}\" in {{type}}. Order: refs → state → redux → router → context → custom hooks → derived → useMemo → useCallback → handlers → useEffect → return",
14077
+ node: violatingStatement,
14078
+ });
13935
14079
  };
13936
14080
 
13937
14081
  const checkFunctionHandler = (node) => {
@@ -13956,6 +14100,7 @@ const reactCodeOrder = {
13956
14100
  },
13957
14101
  meta: {
13958
14102
  docs: { description: "Enforce consistent ordering of code blocks in React components and custom hooks" },
14103
+ fixable: "code",
13959
14104
  schema: [],
13960
14105
  type: "suggestion",
13961
14106
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eslint-plugin-code-style",
3
- "version": "1.3.8",
3
+ "version": "1.3.10",
4
4
  "description": "A custom ESLint plugin for enforcing consistent code formatting and style rules in React/JSX projects",
5
5
  "main": "index.js",
6
6
  "types": "index.d.ts",