lint-rules-alvin 1.0.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.
- package/.vscode/settings.json +9 -0
- package/README.md +13 -0
- package/eslint/configs/astro.js +31 -0
- package/eslint/configs/base.js +22 -0
- package/eslint/configs/custom.js +45 -0
- package/eslint/configs/importX.js +77 -0
- package/eslint/configs/importXTs.js +22 -0
- package/eslint/configs/index.d.ts +12 -0
- package/eslint/configs/index.js +10 -0
- package/eslint/configs/json.js +22 -0
- package/eslint/configs/markdown.js +10 -0
- package/eslint/configs/reactHooks.js +8 -0
- package/eslint/configs/stylistic.js +490 -0
- package/eslint/configs/typescript.js +398 -0
- package/eslint/custom_rules/chain-first-on-newline.js +174 -0
- package/eslint/custom_rules/jsx-multiline-prop-newline.js +99 -0
- package/eslint/custom_rules/jsx-no-single-object-curly-newline.js +100 -0
- package/eslint/custom_rules/max-chain-per-line.js +149 -0
- package/eslint/custom_rules/multiline-paren-newline.js +153 -0
- package/eslint/custom_rules/newline-between-imports.js +77 -0
- package/eslint/custom_rules/unnamed-imports-last.js +84 -0
- package/eslint/presets/astroReact.js +33 -0
- package/eslint/presets/astroReactTs.js +39 -0
- package/eslint/presets/index.d.ts +14 -0
- package/eslint/presets/index.js +2 -0
- package/eslint.config.js +17 -0
- package/package.json +57 -0
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @type {import('eslint').Rule.RuleModule}
|
|
3
|
+
*/
|
|
4
|
+
export const jsxNoSingleObjectCurlyNewlineRule = {
|
|
5
|
+
meta: {
|
|
6
|
+
type: 'layout',
|
|
7
|
+
docs: {
|
|
8
|
+
description: 'Disallows newlines inside curly braces for single object or array expressions in JSX.',
|
|
9
|
+
category: 'Stylistic Issues',
|
|
10
|
+
recommended: false
|
|
11
|
+
},
|
|
12
|
+
fixable: 'code',
|
|
13
|
+
schema: [],
|
|
14
|
+
messages: { error: 'Newlines around single object or array expressions in JSX curly braces are not allowed.' }
|
|
15
|
+
},
|
|
16
|
+
|
|
17
|
+
create(context) {
|
|
18
|
+
|
|
19
|
+
const sourceCode = context.sourceCode;
|
|
20
|
+
|
|
21
|
+
return {
|
|
22
|
+
JSXExpressionContainer(node) {
|
|
23
|
+
|
|
24
|
+
const expression = node.expression;
|
|
25
|
+
|
|
26
|
+
// This rule applies only to single ArrayExpressions or ObjectExpressions.
|
|
27
|
+
if (expression.type !== 'ArrayExpression' && expression.type !== 'ObjectExpression') {
|
|
28
|
+
|
|
29
|
+
return;
|
|
30
|
+
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// If the expression itself isn't multiline, there are no newlines to collapse.
|
|
34
|
+
if (expression.loc.start.line === expression.loc.end.line) {
|
|
35
|
+
|
|
36
|
+
return;
|
|
37
|
+
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const openingBrace = sourceCode.getFirstToken(node); // `{`
|
|
41
|
+
const closingBrace = sourceCode.getLastToken(node); // `}`
|
|
42
|
+
const firstTokenInExpression = sourceCode.getFirstToken(expression);
|
|
43
|
+
const lastTokenInExpression = sourceCode.getLastToken(expression);
|
|
44
|
+
|
|
45
|
+
const hasNewlineBefore = openingBrace.loc.end.line < firstTokenInExpression.loc.start.line;
|
|
46
|
+
const hasNewlineAfter = lastTokenInExpression.loc.end.line < closingBrace.loc.start.line;
|
|
47
|
+
|
|
48
|
+
if (hasNewlineBefore || hasNewlineAfter) {
|
|
49
|
+
|
|
50
|
+
context.report({
|
|
51
|
+
node,
|
|
52
|
+
messageId: 'error',
|
|
53
|
+
fix(fixer) {
|
|
54
|
+
|
|
55
|
+
const fixes = [];
|
|
56
|
+
|
|
57
|
+
if (hasNewlineBefore) {
|
|
58
|
+
|
|
59
|
+
// Range from the end of `{` to the start of the expression's first token.
|
|
60
|
+
const range = [
|
|
61
|
+
openingBrace.range[1],
|
|
62
|
+
firstTokenInExpression.range[0]
|
|
63
|
+
];
|
|
64
|
+
fixes.push(
|
|
65
|
+
fixer.replaceTextRange(
|
|
66
|
+
range,
|
|
67
|
+
''
|
|
68
|
+
)
|
|
69
|
+
);
|
|
70
|
+
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (hasNewlineAfter) {
|
|
74
|
+
|
|
75
|
+
// Range from the end of the expression's last token to the start of `}`.
|
|
76
|
+
const range = [
|
|
77
|
+
lastTokenInExpression.range[1],
|
|
78
|
+
closingBrace.range[0]
|
|
79
|
+
];
|
|
80
|
+
fixes.push(
|
|
81
|
+
fixer.replaceTextRange(
|
|
82
|
+
range,
|
|
83
|
+
''
|
|
84
|
+
)
|
|
85
|
+
);
|
|
86
|
+
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return fixes;
|
|
90
|
+
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
}
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
}
|
|
100
|
+
};
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @type {import('eslint').Rule.RuleModule}
|
|
3
|
+
*/
|
|
4
|
+
export const maxChainPerLineRule = {
|
|
5
|
+
meta: {
|
|
6
|
+
type: 'layout',
|
|
7
|
+
docs: {
|
|
8
|
+
|
|
9
|
+
description: 'Require a newline after each item in a chain if it\'s too long and contains a call.',
|
|
10
|
+
category: 'Stylistic Issues'
|
|
11
|
+
},
|
|
12
|
+
fixable: 'whitespace',
|
|
13
|
+
schema: [
|
|
14
|
+
{
|
|
15
|
+
type: 'object',
|
|
16
|
+
properties: {
|
|
17
|
+
maxChain: {
|
|
18
|
+
type: 'integer',
|
|
19
|
+
minimum: 1
|
|
20
|
+
}
|
|
21
|
+
},
|
|
22
|
+
additionalProperties: false
|
|
23
|
+
}
|
|
24
|
+
],
|
|
25
|
+
messages: { expectedNewline: 'This call chain is too long and should be broken into multiple lines.' }
|
|
26
|
+
},
|
|
27
|
+
create(context) {
|
|
28
|
+
|
|
29
|
+
const options = context.options[0] || {};
|
|
30
|
+
const maxChain = options.maxChain || 2;
|
|
31
|
+
const sourceCode = context.sourceCode;
|
|
32
|
+
|
|
33
|
+
function isChainable(node) {
|
|
34
|
+
|
|
35
|
+
if (!node)
|
|
36
|
+
return false;
|
|
37
|
+
return node.type === 'MemberExpression' || node.type === 'CallExpression';
|
|
38
|
+
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function check(node) {
|
|
42
|
+
|
|
43
|
+
const members = [];
|
|
44
|
+
let callCount = 0;
|
|
45
|
+
let current = node;
|
|
46
|
+
|
|
47
|
+
while (isChainable(current)) {
|
|
48
|
+
|
|
49
|
+
if (current.type === 'CallExpression') {
|
|
50
|
+
|
|
51
|
+
callCount++;
|
|
52
|
+
current = current.callee;
|
|
53
|
+
|
|
54
|
+
} else { // MemberExpression
|
|
55
|
+
|
|
56
|
+
members.unshift(current);
|
|
57
|
+
current = current.object;
|
|
58
|
+
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
}
|
|
62
|
+
const root = current;
|
|
63
|
+
const totalChainLength = members.length + 1;
|
|
64
|
+
|
|
65
|
+
let iter = node;
|
|
66
|
+
while(isChainable(iter)) {
|
|
67
|
+
|
|
68
|
+
if (iter.type === 'CallExpression') {
|
|
69
|
+
|
|
70
|
+
callCount++;
|
|
71
|
+
|
|
72
|
+
}
|
|
73
|
+
iter = iter.type === 'CallExpression'
|
|
74
|
+
? iter.callee
|
|
75
|
+
: iter.object;
|
|
76
|
+
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const isMultiline = root.loc.start.line !== node.loc.end.line;
|
|
80
|
+
|
|
81
|
+
if (callCount > 0) {
|
|
82
|
+
|
|
83
|
+
if (!isMultiline && totalChainLength > maxChain) {
|
|
84
|
+
|
|
85
|
+
context.report({
|
|
86
|
+
node: node,
|
|
87
|
+
messageId: 'expectedNewline',
|
|
88
|
+
fix(fixer) {
|
|
89
|
+
|
|
90
|
+
const fixes = [];
|
|
91
|
+
members.forEach(
|
|
92
|
+
(memberNode) => {
|
|
93
|
+
|
|
94
|
+
const propertyNode = memberNode.property;
|
|
95
|
+
const dotToken = sourceCode.getTokenBefore(propertyNode);
|
|
96
|
+
const rootLine = sourceCode
|
|
97
|
+
.getLines()
|
|
98
|
+
[root.loc.start.line - 1];
|
|
99
|
+
const indent = (rootLine
|
|
100
|
+
.match(/^\s*/)
|
|
101
|
+
[0] || '') + ' ';
|
|
102
|
+
fixes.push(
|
|
103
|
+
fixer.insertTextBefore(
|
|
104
|
+
dotToken,
|
|
105
|
+
'\n' + indent
|
|
106
|
+
)
|
|
107
|
+
);
|
|
108
|
+
|
|
109
|
+
}
|
|
110
|
+
);
|
|
111
|
+
return fixes;
|
|
112
|
+
|
|
113
|
+
}
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
return {
|
|
123
|
+
'MemberExpression:exit'(node) {
|
|
124
|
+
|
|
125
|
+
if (node.parent.type === 'MemberExpression' && node.parent.object === node)
|
|
126
|
+
return;
|
|
127
|
+
if (node.parent.type === 'CallExpression' && node.parent.callee === node)
|
|
128
|
+
return;
|
|
129
|
+
check(node);
|
|
130
|
+
|
|
131
|
+
},
|
|
132
|
+
'CallExpression:exit'(node) {
|
|
133
|
+
|
|
134
|
+
if (node.parent.type === 'MemberExpression' && node.parent.object === node)
|
|
135
|
+
return;
|
|
136
|
+
if (node.parent.type === 'CallExpression' && node.parent.callee === node)
|
|
137
|
+
return;
|
|
138
|
+
|
|
139
|
+
if (isChainable(node.callee)) {
|
|
140
|
+
|
|
141
|
+
check(node);
|
|
142
|
+
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
}
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
}
|
|
149
|
+
};
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @type {import('eslint').Rule.RuleModule}
|
|
3
|
+
*/
|
|
4
|
+
export const multilineParenNewlineRule = {
|
|
5
|
+
meta: {
|
|
6
|
+
type: 'layout',
|
|
7
|
+
docs: {
|
|
8
|
+
description: 'Require newlines inside parentheses of call expressions that are multiline',
|
|
9
|
+
category: 'Stylistic Issues',
|
|
10
|
+
recommended: false
|
|
11
|
+
},
|
|
12
|
+
fixable: 'whitespace',
|
|
13
|
+
schema: [
|
|
14
|
+
{
|
|
15
|
+
type: 'object',
|
|
16
|
+
properties: {
|
|
17
|
+
singleArgument: {
|
|
18
|
+
type: 'boolean',
|
|
19
|
+
default: false
|
|
20
|
+
}
|
|
21
|
+
},
|
|
22
|
+
additionalProperties: false
|
|
23
|
+
}
|
|
24
|
+
],
|
|
25
|
+
messages: {
|
|
26
|
+
expectedNewlineAfterParen: 'Expected a newline after \'(\'.',
|
|
27
|
+
expectedNewlineBeforeParen: 'Expected a newline before \')\'.'
|
|
28
|
+
}
|
|
29
|
+
},
|
|
30
|
+
|
|
31
|
+
create(context) {
|
|
32
|
+
|
|
33
|
+
const sourceCode = context.sourceCode;
|
|
34
|
+
const options = context.options[0] || {};
|
|
35
|
+
const allowSingleArgumentOnSameLine = options.singleArgument === true;
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* @param {import('estree').CallExpression} node
|
|
39
|
+
*/
|
|
40
|
+
function check(node) {
|
|
41
|
+
|
|
42
|
+
// Get the opening parenthesis token.
|
|
43
|
+
const openParen = sourceCode.getTokenAfter(node.callee);
|
|
44
|
+
|
|
45
|
+
// Get the closing parenthesis token.
|
|
46
|
+
const closeParen = sourceCode.getLastToken(node);
|
|
47
|
+
|
|
48
|
+
// If we can't find the parentheses, or they aren't parentheses, exit.
|
|
49
|
+
if (!openParen || openParen.value !== '(' || !closeParen || closeParen.value !== ')') {
|
|
50
|
+
|
|
51
|
+
return;
|
|
52
|
+
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// If the parentheses are on the same line, this rule doesn't apply.
|
|
56
|
+
if (openParen.loc.start.line === closeParen.loc.end.line) {
|
|
57
|
+
|
|
58
|
+
return;
|
|
59
|
+
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Handle the singleArgument exception
|
|
63
|
+
if (
|
|
64
|
+
allowSingleArgumentOnSameLine
|
|
65
|
+
&& node.arguments.length === 1
|
|
66
|
+
) {
|
|
67
|
+
|
|
68
|
+
const arg = node.arguments[0];
|
|
69
|
+
const isObjectOrArray = arg.type === 'ObjectExpression' || arg.type === 'ArrayExpression';
|
|
70
|
+
|
|
71
|
+
if (isObjectOrArray) {
|
|
72
|
+
|
|
73
|
+
// For this exception, we *disallow* newlines between parens and the single argument.
|
|
74
|
+
const firstTokenOfArg = sourceCode.getFirstToken(arg);
|
|
75
|
+
if (openParen.loc.end.line !== firstTokenOfArg.loc.start.line) {
|
|
76
|
+
|
|
77
|
+
context.report({
|
|
78
|
+
node: openParen,
|
|
79
|
+
message: 'Unexpected newline after \'(\' for single object/array argument.',
|
|
80
|
+
fix: (fixer) => fixer.removeRange([
|
|
81
|
+
openParen.range[1],
|
|
82
|
+
firstTokenOfArg.range[0]
|
|
83
|
+
])
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const lastTokenOfArg = sourceCode.getLastToken(arg);
|
|
89
|
+
if (lastTokenOfArg.loc.end.line !== closeParen.loc.start.line) {
|
|
90
|
+
|
|
91
|
+
context.report({
|
|
92
|
+
node: closeParen,
|
|
93
|
+
message: 'Unexpected newline before \')\' for single object/array argument.',
|
|
94
|
+
fix: (fixer) => fixer.removeRange([
|
|
95
|
+
lastTokenOfArg.range[1],
|
|
96
|
+
closeParen.range[0]
|
|
97
|
+
])
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
return; // End processing for this node
|
|
103
|
+
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Check for a newline after the opening parenthesis.
|
|
109
|
+
const firstArg = node.arguments[0];
|
|
110
|
+
if (firstArg) {
|
|
111
|
+
|
|
112
|
+
const firstTokenOfFirstArg = sourceCode.getFirstToken(firstArg);
|
|
113
|
+
if (openParen.loc.end.line === firstTokenOfFirstArg.loc.start.line) {
|
|
114
|
+
|
|
115
|
+
context.report({
|
|
116
|
+
node: openParen,
|
|
117
|
+
messageId: 'expectedNewlineAfterParen',
|
|
118
|
+
fix: (fixer) => fixer.insertTextAfter(
|
|
119
|
+
openParen,
|
|
120
|
+
'\n'
|
|
121
|
+
)
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Check for a newline before the closing parenthesis.
|
|
129
|
+
const lastArg = node.arguments[node.arguments.length - 1];
|
|
130
|
+
if (lastArg) {
|
|
131
|
+
|
|
132
|
+
const lastTokenOfLastArg = sourceCode.getLastToken(lastArg);
|
|
133
|
+
if (lastTokenOfLastArg.loc.end.line === closeParen.loc.start.line) {
|
|
134
|
+
|
|
135
|
+
context.report({
|
|
136
|
+
node: closeParen,
|
|
137
|
+
messageId: 'expectedNewlineBeforeParen',
|
|
138
|
+
fix: (fixer) => fixer.insertTextBefore(
|
|
139
|
+
closeParen,
|
|
140
|
+
'\n'
|
|
141
|
+
)
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
return { CallExpression: check };
|
|
151
|
+
|
|
152
|
+
}
|
|
153
|
+
};
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @type {import('eslint').Rule.RuleModule}
|
|
3
|
+
*/
|
|
4
|
+
export const newlineBetweenImportsRule = {
|
|
5
|
+
meta: {
|
|
6
|
+
type: 'layout',
|
|
7
|
+
docs: {
|
|
8
|
+
description: 'Enforce newlines between import specifiers',
|
|
9
|
+
category: 'Stylistic Issues',
|
|
10
|
+
recommended: false
|
|
11
|
+
},
|
|
12
|
+
fixable: 'whitespace',
|
|
13
|
+
schema: [
|
|
14
|
+
{
|
|
15
|
+
type: 'object',
|
|
16
|
+
properties: {
|
|
17
|
+
minItems: {
|
|
18
|
+
type: 'integer',
|
|
19
|
+
minimum: 2
|
|
20
|
+
}
|
|
21
|
+
},
|
|
22
|
+
additionalProperties: false
|
|
23
|
+
}
|
|
24
|
+
],
|
|
25
|
+
messages: { error: 'There should be a newline between import specifiers.' }
|
|
26
|
+
},
|
|
27
|
+
|
|
28
|
+
create(context) {
|
|
29
|
+
|
|
30
|
+
const sourceCode = context.sourceCode;
|
|
31
|
+
const { minItems = 2 } = context.options[0] || {};
|
|
32
|
+
|
|
33
|
+
return {
|
|
34
|
+
ImportDeclaration(node) {
|
|
35
|
+
|
|
36
|
+
const specifiers = node
|
|
37
|
+
.specifiers
|
|
38
|
+
.filter((specifier) => specifier.type === 'ImportSpecifier');
|
|
39
|
+
|
|
40
|
+
if (specifiers.length < minItems) {
|
|
41
|
+
|
|
42
|
+
return;
|
|
43
|
+
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
for (let i = 0; i < specifiers.length - 1; i++) {
|
|
47
|
+
|
|
48
|
+
const currentSpecifier = specifiers[i];
|
|
49
|
+
const nextSpecifier = specifiers[i + 1];
|
|
50
|
+
|
|
51
|
+
const lastTokenOfCurrent = sourceCode.getLastToken(currentSpecifier);
|
|
52
|
+
const firstTokenOfNext = sourceCode.getFirstToken(nextSpecifier);
|
|
53
|
+
|
|
54
|
+
if (lastTokenOfCurrent.loc.end.line === firstTokenOfNext.loc.start.line) {
|
|
55
|
+
|
|
56
|
+
context.report({
|
|
57
|
+
node: nextSpecifier,
|
|
58
|
+
messageId: 'error',
|
|
59
|
+
fix(fixer) {
|
|
60
|
+
|
|
61
|
+
return fixer.insertTextBefore(
|
|
62
|
+
nextSpecifier,
|
|
63
|
+
'\n'
|
|
64
|
+
);
|
|
65
|
+
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
}
|
|
77
|
+
};
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @type {import('eslint').Rule.RuleModule}
|
|
3
|
+
*/
|
|
4
|
+
export const unnamedImportsLastRule = {
|
|
5
|
+
meta: {
|
|
6
|
+
type: 'layout',
|
|
7
|
+
docs: {
|
|
8
|
+
description: 'Enforce that unnamed imports (side-effect imports) are last',
|
|
9
|
+
category: 'Stylistic Issues',
|
|
10
|
+
recommended: false
|
|
11
|
+
},
|
|
12
|
+
fixable: 'code',
|
|
13
|
+
schema: [],
|
|
14
|
+
messages: { error: 'Unnamed (side-effect) imports should be at the bottom of the import block.' }
|
|
15
|
+
},
|
|
16
|
+
|
|
17
|
+
create(context) {
|
|
18
|
+
|
|
19
|
+
return {
|
|
20
|
+
'Program:exit'(program) {
|
|
21
|
+
|
|
22
|
+
const sourceCode = context.sourceCode;
|
|
23
|
+
const allImports = program
|
|
24
|
+
.body
|
|
25
|
+
.filter((node) => node.type === 'ImportDeclaration');
|
|
26
|
+
|
|
27
|
+
if (allImports.length < 2) {
|
|
28
|
+
|
|
29
|
+
return;
|
|
30
|
+
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const regularImports = allImports.filter((node) => node.specifiers.length > 0);
|
|
34
|
+
const sideEffectImports = allImports.filter((node) => node.specifiers.length === 0);
|
|
35
|
+
|
|
36
|
+
if (regularImports.length === 0 || sideEffectImports.length === 0) {
|
|
37
|
+
|
|
38
|
+
return;
|
|
39
|
+
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const lastRegularImport = regularImports[regularImports.length - 1];
|
|
43
|
+
|
|
44
|
+
const misplacedImports = sideEffectImports.filter((node) => node.range[0] < lastRegularImport.range[0]);
|
|
45
|
+
|
|
46
|
+
if (misplacedImports.length === 0) {
|
|
47
|
+
|
|
48
|
+
return;
|
|
49
|
+
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Report on the first misplaced import
|
|
53
|
+
const firstMisplaced = misplacedImports[0];
|
|
54
|
+
|
|
55
|
+
context.report({
|
|
56
|
+
node: firstMisplaced,
|
|
57
|
+
messageId: 'error',
|
|
58
|
+
fix(fixer) {
|
|
59
|
+
|
|
60
|
+
const sortedImports = [
|
|
61
|
+
...regularImports,
|
|
62
|
+
...sideEffectImports
|
|
63
|
+
];
|
|
64
|
+
|
|
65
|
+
const importTexts = sortedImports.map((node) => sourceCode.getText(node));
|
|
66
|
+
|
|
67
|
+
const range = [
|
|
68
|
+
allImports[0].range[0],
|
|
69
|
+
allImports[allImports.length - 1].range[1]
|
|
70
|
+
];
|
|
71
|
+
|
|
72
|
+
return fixer.replaceTextRange(
|
|
73
|
+
range,
|
|
74
|
+
importTexts.join('\n')
|
|
75
|
+
);
|
|
76
|
+
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
}
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
}
|
|
84
|
+
};
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { defineConfig } from 'eslint/config';
|
|
2
|
+
import { importX as eslintPluginImportX } from 'eslint-plugin-import-x';
|
|
3
|
+
import {
|
|
4
|
+
base,
|
|
5
|
+
astro,
|
|
6
|
+
importX,
|
|
7
|
+
reactHooks,
|
|
8
|
+
stylistic,
|
|
9
|
+
custom
|
|
10
|
+
} from '../configs/index.js';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* The `ESLint` Astro/React config with typescript.
|
|
14
|
+
*/
|
|
15
|
+
export const astroReact = defineConfig(
|
|
16
|
+
base,
|
|
17
|
+
astro,
|
|
18
|
+
{
|
|
19
|
+
...eslintPluginImportX.configs['flat/react'],
|
|
20
|
+
...importX
|
|
21
|
+
},
|
|
22
|
+
reactHooks,
|
|
23
|
+
stylistic,
|
|
24
|
+
custom,
|
|
25
|
+
{
|
|
26
|
+
name: 'eslint-plugin-astro-stylistic-override',
|
|
27
|
+
files: [ '**/*.astro' ],
|
|
28
|
+
rules: {
|
|
29
|
+
'@stylistic/jsx-one-expression-per-line': 'off',
|
|
30
|
+
'@stylistic/jsx-curly-brace-presence': 'off'
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
);
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import {
|
|
2
|
+
defineConfig,
|
|
3
|
+
globalIgnores
|
|
4
|
+
} from 'eslint/config';
|
|
5
|
+
import { importX as eslintPluginImportX } from 'eslint-plugin-import-x';
|
|
6
|
+
import {
|
|
7
|
+
base,
|
|
8
|
+
custom,
|
|
9
|
+
astro,
|
|
10
|
+
typescript,
|
|
11
|
+
reactHooks,
|
|
12
|
+
stylistic,
|
|
13
|
+
importXTs
|
|
14
|
+
} from '../configs/index.js';
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* The `ESLint` Astro/React config with typescript.
|
|
18
|
+
*/
|
|
19
|
+
export const astroReactTs = defineConfig(
|
|
20
|
+
base,
|
|
21
|
+
globalIgnores([ '**/*.astro/*.ts' ]),
|
|
22
|
+
typescript,
|
|
23
|
+
astro,
|
|
24
|
+
{
|
|
25
|
+
...eslintPluginImportX.configs['flat/react'],
|
|
26
|
+
...importXTs
|
|
27
|
+
},
|
|
28
|
+
reactHooks,
|
|
29
|
+
stylistic,
|
|
30
|
+
custom,
|
|
31
|
+
{
|
|
32
|
+
name: 'eslint-plugin-astro-stylistic-override',
|
|
33
|
+
files: [ '**/*.astro' ],
|
|
34
|
+
rules: {
|
|
35
|
+
'@stylistic/jsx-one-expression-per-line': 'off',
|
|
36
|
+
'@stylistic/jsx-curly-brace-presence': 'off'
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
);
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { Linter } from 'eslint';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* ESLint preset for Astro/React with JavaScript.
|
|
5
|
+
*
|
|
6
|
+
* This package uses ESLint's flat config format, so the preset is an array
|
|
7
|
+
* of flat-config items.
|
|
8
|
+
*/
|
|
9
|
+
export declare const astroReact: readonly Linter.Config[];
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* ESLint preset for Astro/React with TypeScript.
|
|
13
|
+
*/
|
|
14
|
+
export declare const astroReactTs: readonly Linter.Config[];
|
package/eslint.config.js
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { defineConfig, globalIgnores } from 'eslint/config';
|
|
2
|
+
import {
|
|
3
|
+
base,
|
|
4
|
+
importX,
|
|
5
|
+
json,
|
|
6
|
+
stylistic,
|
|
7
|
+
custom
|
|
8
|
+
} from './eslint/configs/index.js';
|
|
9
|
+
|
|
10
|
+
export default defineConfig(
|
|
11
|
+
globalIgnores(['**/*.d.ts']),
|
|
12
|
+
base,
|
|
13
|
+
importX,
|
|
14
|
+
stylistic,
|
|
15
|
+
json,
|
|
16
|
+
custom
|
|
17
|
+
);
|