@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,2 @@
1
+ import type { Rule } from 'eslint';
2
+ export declare const noCombinator: Rule.RuleModule;
@@ -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 css.props and css.global',
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 sorted = [...properties].sort((a, b) => {
64
- const indexA = getPropertyIndex(a, isTopLevel);
65
- const indexB = getPropertyIndex(b, isTopLevel);
66
- return indexA === null || indexB === null ? 0 : indexA - indexB;
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: getPropertyName(prop),
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
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@plumeria/eslint-plugin",
3
- "version": "6.3.1",
3
+ "version": "7.0.0",
4
4
  "description": "Plumeria ESLint plugin",
5
5
  "author": "Refirst 11",
6
6
  "license": "MIT",