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.
- package/index.js +169 -24
- 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
|
-
//
|
|
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
|
-
//
|
|
13898
|
-
const
|
|
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
|
-
|
|
13948
|
+
// Filter to only categorizable statements for order checking
|
|
13949
|
+
const categorizableStatements = statements.filter(isCategorizableStatement);
|
|
13904
13950
|
|
|
13905
|
-
|
|
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
|
|
13956
|
+
let violatingStatement = null;
|
|
13957
|
+
let violatingCategory = null;
|
|
13958
|
+
let previousCategory = null;
|
|
13908
13959
|
|
|
13909
|
-
for (const statement of
|
|
13960
|
+
for (const statement of categorizableStatements) {
|
|
13910
13961
|
const category = getStatementCategoryHandler(statement, propNames);
|
|
13911
13962
|
|
|
13912
|
-
// Skip unknown statements
|
|
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
|
-
|
|
13918
|
-
|
|
13919
|
-
|
|
13920
|
-
|
|
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