@plumeria/eslint-plugin 6.3.1 → 7.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/dist/index.js
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.plumeria = void 0;
|
|
4
|
+
const no_combinator_1 = require("./rules/no-combinator");
|
|
4
5
|
const no_destructure_1 = require("./rules/no-destructure");
|
|
5
6
|
const no_inner_call_1 = require("./rules/no-inner-call");
|
|
6
7
|
const no_unused_keys_1 = require("./rules/no-unused-keys");
|
|
7
8
|
const sort_properties_1 = require("./rules/sort-properties");
|
|
8
9
|
const validate_values_1 = require("./rules/validate-values");
|
|
9
10
|
const rules = {
|
|
11
|
+
'no-combinator': no_combinator_1.noCombinator,
|
|
10
12
|
'no-destructure': no_destructure_1.noDestructure,
|
|
11
13
|
'no-inner-call': no_inner_call_1.noInnerCall,
|
|
12
14
|
'no-unused-keys': no_unused_keys_1.noUnusedKeys,
|
|
@@ -17,6 +19,7 @@ const configs = {
|
|
|
17
19
|
recommended: {
|
|
18
20
|
plugins: ['@plumeria'],
|
|
19
21
|
rules: {
|
|
22
|
+
'@plumeria/no-combinator': 'error',
|
|
20
23
|
'@plumeria/no-destructure': 'error',
|
|
21
24
|
'@plumeria/no-inner-call': 'error',
|
|
22
25
|
'@plumeria/no-unused-keys': 'warn',
|
|
@@ -33,6 +36,7 @@ const flatConfigs = {
|
|
|
33
36
|
},
|
|
34
37
|
},
|
|
35
38
|
rules: {
|
|
39
|
+
'@plumeria/no-combinator': 'error',
|
|
36
40
|
'@plumeria/no-destructure': 'error',
|
|
37
41
|
'@plumeria/no-inner-call': 'error',
|
|
38
42
|
'@plumeria/no-unused-keys': 'warn',
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.noCombinator = void 0;
|
|
4
|
+
exports.noCombinator = {
|
|
5
|
+
meta: {
|
|
6
|
+
type: 'problem',
|
|
7
|
+
docs: {
|
|
8
|
+
description: 'Disallow combinators >, +, ~ and descendant combinator (space) unless inside functional pseudo-classes.',
|
|
9
|
+
},
|
|
10
|
+
messages: {
|
|
11
|
+
noCombinator: 'Combinator "{{combinator}}" is not allowed unless inside functional pseudo-classes.',
|
|
12
|
+
},
|
|
13
|
+
schema: [],
|
|
14
|
+
},
|
|
15
|
+
create(context) {
|
|
16
|
+
const plumeriaAliases = {};
|
|
17
|
+
function isCombinatorAllowed(selector) {
|
|
18
|
+
const s = selector.trim();
|
|
19
|
+
const len = s.length;
|
|
20
|
+
let i = 0;
|
|
21
|
+
while (i < len) {
|
|
22
|
+
const char = s[i];
|
|
23
|
+
if (char === '"' || char === "'") {
|
|
24
|
+
i = skipString(s, i);
|
|
25
|
+
continue;
|
|
26
|
+
}
|
|
27
|
+
if (char === '(') {
|
|
28
|
+
i = skipBlock(s, i, '(', ')');
|
|
29
|
+
continue;
|
|
30
|
+
}
|
|
31
|
+
if (char === '[') {
|
|
32
|
+
i = skipBlock(s, i, '[', ']');
|
|
33
|
+
continue;
|
|
34
|
+
}
|
|
35
|
+
if (char === '>' || char === '+' || char === '~') {
|
|
36
|
+
return false;
|
|
37
|
+
}
|
|
38
|
+
if (isSpace(char)) {
|
|
39
|
+
let next = i + 1;
|
|
40
|
+
while (next < len && isSpace(s[next])) {
|
|
41
|
+
next++;
|
|
42
|
+
}
|
|
43
|
+
if (next < len) {
|
|
44
|
+
const nextChar = s[next];
|
|
45
|
+
const prevChar = s[i - 1];
|
|
46
|
+
if (!isCombinatorOrSeparator(prevChar) &&
|
|
47
|
+
!isCombinatorOrSeparator(nextChar)) {
|
|
48
|
+
return false;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
i = next;
|
|
52
|
+
continue;
|
|
53
|
+
}
|
|
54
|
+
i++;
|
|
55
|
+
}
|
|
56
|
+
return true;
|
|
57
|
+
}
|
|
58
|
+
function skipString(s, start) {
|
|
59
|
+
const quote = s[start];
|
|
60
|
+
let i = start + 1;
|
|
61
|
+
while (i < s.length) {
|
|
62
|
+
if (s[i] === '\\') {
|
|
63
|
+
i += 2;
|
|
64
|
+
continue;
|
|
65
|
+
}
|
|
66
|
+
if (s[i] === quote) {
|
|
67
|
+
return i + 1;
|
|
68
|
+
}
|
|
69
|
+
i++;
|
|
70
|
+
}
|
|
71
|
+
return i;
|
|
72
|
+
}
|
|
73
|
+
function skipBlock(s, start, open, close) {
|
|
74
|
+
let depth = 1;
|
|
75
|
+
let i = start + 1;
|
|
76
|
+
while (i < s.length && depth > 0) {
|
|
77
|
+
const char = s[i];
|
|
78
|
+
if (char === '\\') {
|
|
79
|
+
i += 2;
|
|
80
|
+
continue;
|
|
81
|
+
}
|
|
82
|
+
if (char === '"' || char === "'") {
|
|
83
|
+
i = skipString(s, i);
|
|
84
|
+
continue;
|
|
85
|
+
}
|
|
86
|
+
if (char === open) {
|
|
87
|
+
depth++;
|
|
88
|
+
}
|
|
89
|
+
else if (char === close) {
|
|
90
|
+
depth--;
|
|
91
|
+
}
|
|
92
|
+
i++;
|
|
93
|
+
}
|
|
94
|
+
return i;
|
|
95
|
+
}
|
|
96
|
+
function isSpace(char) {
|
|
97
|
+
return char === ' ' || char === '\t' || char === '\n' || char === '\r';
|
|
98
|
+
}
|
|
99
|
+
function isCombinatorOrSeparator(char) {
|
|
100
|
+
return (char === '>' ||
|
|
101
|
+
char === '+' ||
|
|
102
|
+
char === '~' ||
|
|
103
|
+
char === ',' ||
|
|
104
|
+
char === undefined);
|
|
105
|
+
}
|
|
106
|
+
return {
|
|
107
|
+
ImportDeclaration(node) {
|
|
108
|
+
if (node.source.value === '@plumeria/core') {
|
|
109
|
+
node.specifiers.forEach((specifier) => {
|
|
110
|
+
if (specifier.type === 'ImportNamespaceSpecifier' ||
|
|
111
|
+
specifier.type === 'ImportDefaultSpecifier') {
|
|
112
|
+
plumeriaAliases[specifier.local.name] = 'NAMESPACE';
|
|
113
|
+
}
|
|
114
|
+
else if (specifier.type === 'ImportSpecifier') {
|
|
115
|
+
const importedName = specifier.imported.type === 'Identifier'
|
|
116
|
+
? specifier.imported.name
|
|
117
|
+
: String(specifier.imported.value);
|
|
118
|
+
plumeriaAliases[specifier.local.name] = importedName;
|
|
119
|
+
}
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
},
|
|
123
|
+
CallExpression(node) {
|
|
124
|
+
if (node.callee.type === 'MemberExpression') {
|
|
125
|
+
if (node.callee.object.type === 'Identifier' &&
|
|
126
|
+
plumeriaAliases[node.callee.object.name] === 'NAMESPACE') {
|
|
127
|
+
const propertyName = node.callee.property.type === 'Identifier'
|
|
128
|
+
? node.callee.property.name
|
|
129
|
+
: null;
|
|
130
|
+
if (propertyName === 'create' ||
|
|
131
|
+
propertyName === 'createStatic' ||
|
|
132
|
+
propertyName === 'variants') {
|
|
133
|
+
node.arguments.forEach((arg) => {
|
|
134
|
+
if (arg.type === 'ObjectExpression') {
|
|
135
|
+
checkForCombinatorsRecursively(arg);
|
|
136
|
+
}
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
else if (node.callee.type === 'Identifier') {
|
|
142
|
+
const alias = plumeriaAliases[node.callee.name];
|
|
143
|
+
if (alias === 'create' ||
|
|
144
|
+
alias === 'createStatic' ||
|
|
145
|
+
alias === 'variants') {
|
|
146
|
+
node.arguments.forEach((arg) => {
|
|
147
|
+
if (arg.type === 'ObjectExpression') {
|
|
148
|
+
checkForCombinatorsRecursively(arg);
|
|
149
|
+
}
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
},
|
|
154
|
+
};
|
|
155
|
+
function checkForCombinatorsRecursively(node) {
|
|
156
|
+
for (const prop of node.properties) {
|
|
157
|
+
if (prop.type === 'Property') {
|
|
158
|
+
let keyName = '';
|
|
159
|
+
if (prop.key.type === 'Identifier') {
|
|
160
|
+
keyName = prop.key.name;
|
|
161
|
+
}
|
|
162
|
+
else if (prop.key.type === 'Literal') {
|
|
163
|
+
keyName = String(prop.key.value);
|
|
164
|
+
}
|
|
165
|
+
if (keyName) {
|
|
166
|
+
if (keyName.includes('>') ||
|
|
167
|
+
keyName.includes('+') ||
|
|
168
|
+
keyName.includes('~') ||
|
|
169
|
+
keyName.includes(' ') ||
|
|
170
|
+
keyName.includes('\t') ||
|
|
171
|
+
keyName.includes('\n')) {
|
|
172
|
+
if (!isCombinatorAllowed(keyName)) {
|
|
173
|
+
let found = '';
|
|
174
|
+
if (keyName.includes('>'))
|
|
175
|
+
found = '>';
|
|
176
|
+
else if (keyName.includes('+'))
|
|
177
|
+
found = '+';
|
|
178
|
+
else if (keyName.includes('~'))
|
|
179
|
+
found = '~';
|
|
180
|
+
else if (keyName.includes(' ') ||
|
|
181
|
+
keyName.includes('\t') ||
|
|
182
|
+
keyName.includes('\n'))
|
|
183
|
+
found = '(space)';
|
|
184
|
+
context.report({
|
|
185
|
+
node: prop.key,
|
|
186
|
+
messageId: 'noCombinator',
|
|
187
|
+
data: { combinator: found },
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
if (prop.value.type === 'ObjectExpression') {
|
|
193
|
+
checkForCombinatorsRecursively(prop.value);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
},
|
|
199
|
+
};
|
|
@@ -5,7 +5,7 @@ exports.noDestructure = {
|
|
|
5
5
|
meta: {
|
|
6
6
|
type: 'problem',
|
|
7
7
|
docs: {
|
|
8
|
-
description: 'Disallow destructuring
|
|
8
|
+
description: 'Disallow destructuring API',
|
|
9
9
|
},
|
|
10
10
|
messages: {
|
|
11
11
|
noDestructure: 'Do not destructure "{{property}}" from "{{object}}". Use dot notation instead.',
|
|
@@ -59,25 +59,51 @@ exports.sortProperties = {
|
|
|
59
59
|
ObjectExpression(node) {
|
|
60
60
|
const sourceCode = getSourceCode(context);
|
|
61
61
|
const isTopLevel = !node.parent || node.parent.type !== 'Property';
|
|
62
|
-
const properties = node.properties.filter((prop) => 'key' in prop && !!prop.key);
|
|
63
|
-
const
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
62
|
+
const properties = node.properties.filter((prop) => ('key' in prop && !!prop.key) || prop.type === 'SpreadElement');
|
|
63
|
+
const chunks = [];
|
|
64
|
+
let currentChunk = [];
|
|
65
|
+
properties.forEach((prop) => {
|
|
66
|
+
if (prop.type === 'SpreadElement') {
|
|
67
|
+
if (currentChunk.length > 0)
|
|
68
|
+
chunks.push(currentChunk);
|
|
69
|
+
chunks.push([prop]);
|
|
70
|
+
currentChunk = [];
|
|
71
|
+
}
|
|
72
|
+
else {
|
|
73
|
+
currentChunk.push(prop);
|
|
74
|
+
}
|
|
67
75
|
});
|
|
76
|
+
if (currentChunk.length > 0)
|
|
77
|
+
chunks.push(currentChunk);
|
|
78
|
+
const sorted = chunks
|
|
79
|
+
.map((chunk) => {
|
|
80
|
+
if (chunk.length === 1 && chunk[0].type === 'SpreadElement') {
|
|
81
|
+
return chunk;
|
|
82
|
+
}
|
|
83
|
+
return [...chunk].sort((a, b) => {
|
|
84
|
+
const indexA = getPropertyIndex(a, isTopLevel);
|
|
85
|
+
const indexB = getPropertyIndex(b, isTopLevel);
|
|
86
|
+
return indexA === null || indexB === null ? 0 : indexA - indexB;
|
|
87
|
+
});
|
|
88
|
+
})
|
|
89
|
+
.flat();
|
|
68
90
|
const misordered = properties.filter((prop, i) => prop !== sorted[i]);
|
|
69
91
|
if (misordered.length === 0)
|
|
70
92
|
return;
|
|
71
93
|
const match = sourceCode.getText(node).match(/^{\s*\n(\s*)/);
|
|
72
94
|
const indent = match ? match[1] : '';
|
|
73
95
|
const lineEnding = match ? '\n' : ' ';
|
|
96
|
+
const closingIndentMatch = sourceCode.getText(node).match(/\n(\s*)}$/);
|
|
97
|
+
const closingIndent = closingIndentMatch ? closingIndentMatch[1] : '';
|
|
74
98
|
misordered.forEach((prop) => {
|
|
75
99
|
context.report({
|
|
76
100
|
node: prop,
|
|
77
101
|
messageId: 'sortProperties',
|
|
78
102
|
data: {
|
|
79
103
|
position: String(sorted.indexOf(prop) + 1),
|
|
80
|
-
property:
|
|
104
|
+
property: prop.type === 'SpreadElement'
|
|
105
|
+
? '...spread'
|
|
106
|
+
: getPropertyName(prop),
|
|
81
107
|
},
|
|
82
108
|
fix(fixer) {
|
|
83
109
|
const newText = sorted
|
|
@@ -88,7 +114,7 @@ exports.sortProperties = {
|
|
|
88
114
|
return fixer.replaceTextRange([
|
|
89
115
|
node.range[0] + 1,
|
|
90
116
|
node.range[1] - 1,
|
|
91
|
-
], `${lineEnding}${newText}${lineEnding}`);
|
|
117
|
+
], `${lineEnding}${newText}${lineEnding}${closingIndent}`);
|
|
92
118
|
},
|
|
93
119
|
});
|
|
94
120
|
});
|