@tenphi/eslint-plugin-tasty 0.8.0 → 0.9.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/README.md +2 -2
- package/dist/configs.js +1 -1
- package/dist/configs.js.map +1 -1
- package/dist/constants.js +191 -3
- package/dist/constants.js.map +1 -1
- package/dist/fix-utils.js +92 -0
- package/dist/fix-utils.js.map +1 -0
- package/dist/rules/consistent-token-usage.js +59 -12
- package/dist/rules/consistent-token-usage.js.map +1 -1
- package/dist/rules/no-important.js +12 -4
- package/dist/rules/no-important.js.map +1 -1
- package/dist/rules/no-nested-selector.js +2 -2
- package/dist/rules/no-nested-selector.js.map +1 -1
- package/dist/rules/no-own-at-root.js +9 -1
- package/dist/rules/no-own-at-root.js.map +1 -1
- package/dist/rules/no-raw-color-values.js +23 -7
- package/dist/rules/no-raw-color-values.js.map +1 -1
- package/dist/rules/prefer-auto-calc.js +23 -1
- package/dist/rules/prefer-auto-calc.js.map +1 -1
- package/dist/rules/prefer-custom-property-syntax.js +79 -4
- package/dist/rules/prefer-custom-property-syntax.js.map +1 -1
- package/dist/rules/prefer-directional-shorthand.js +5 -0
- package/dist/rules/prefer-directional-shorthand.js.map +1 -1
- package/dist/rules/prefer-hide.js +15 -9
- package/dist/rules/prefer-hide.js.map +1 -1
- package/dist/rules/prefer-shorthand-property.js +5 -0
- package/dist/rules/prefer-shorthand-property.js.map +1 -1
- package/dist/rules/valid-default-state-order.js +36 -5
- package/dist/rules/valid-default-state-order.js.map +1 -1
- package/dist/rules/valid-radius-shape.js +18 -2
- package/dist/rules/valid-radius-shape.js.map +1 -1
- package/dist/rules/valid-transition.js +37 -5
- package/dist/rules/valid-transition.js.map +1 -1
- package/package.json +1 -1
|
@@ -8,6 +8,7 @@ var prefer_shorthand_property_default = createRule({
|
|
|
8
8
|
name: "prefer-shorthand-property",
|
|
9
9
|
meta: {
|
|
10
10
|
type: "suggestion",
|
|
11
|
+
fixable: "code",
|
|
11
12
|
docs: { description: "Suggest tasty shorthand when a native CSS property with a tasty alternative is used" },
|
|
12
13
|
messages: { preferShorthand: "Prefer tasty shorthand '{{alternative}}' instead of '{{native}}'." },
|
|
13
14
|
schema: []
|
|
@@ -28,6 +29,10 @@ var prefer_shorthand_property_default = createRule({
|
|
|
28
29
|
data: {
|
|
29
30
|
native: key,
|
|
30
31
|
alternative: mapping.hint
|
|
32
|
+
},
|
|
33
|
+
fix(fixer) {
|
|
34
|
+
if (!mapping.safeFix) return null;
|
|
35
|
+
return fixer.replaceText(prop.key, mapping.property);
|
|
31
36
|
}
|
|
32
37
|
});
|
|
33
38
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"prefer-shorthand-property.js","names":[],"sources":["../../src/rules/prefer-shorthand-property.ts"],"sourcesContent":["import type { TSESTree } from '@typescript-eslint/utils';\nimport { createRule } from '../create-rule.js';\nimport { TastyContext, styleObjectListeners } from '../context.js';\nimport { getKeyName } from '../utils.js';\nimport { SHORTHAND_MAPPING } from '../constants.js';\n\ntype MessageIds = 'preferShorthand';\n\nexport default createRule<[], MessageIds>({\n name: 'prefer-shorthand-property',\n meta: {\n type: 'suggestion',\n docs: {\n description:\n 'Suggest tasty shorthand when a native CSS property with a tasty alternative is used',\n },\n messages: {\n preferShorthand:\n \"Prefer tasty shorthand '{{alternative}}' instead of '{{native}}'.\",\n },\n schema: [],\n },\n defaultOptions: [],\n create(context) {\n const ctx = new TastyContext(context);\n\n function handleStyleObject(node: TSESTree.ObjectExpression) {\n if (!ctx.isStyleObject(node)) return;\n\n for (const prop of node.properties) {\n if (prop.type !== 'Property' || prop.computed) continue;\n\n const key = getKeyName(prop.key);\n if (key === null) continue;\n\n const mapping = SHORTHAND_MAPPING[key];\n if (mapping) {\n context.report({\n node: prop.key,\n messageId: 'preferShorthand',\n data: { native: key, alternative: mapping.hint },\n });\n }\n }\n }\n\n return {\n ImportDeclaration(node) {\n ctx.trackImport(node);\n },\n ...styleObjectListeners(handleStyleObject),\n };\n },\n});\n"],"mappings":";;;;;;AAQA,wCAAe,WAA2B;CACxC,MAAM;CACN,MAAM;EACJ,MAAM;EACN,MAAM,EACJ,aACE,uFACH;EACD,UAAU,EACR,iBACE,qEACH;EACD,QAAQ,EAAE;EACX;CACD,gBAAgB,EAAE;CAClB,OAAO,SAAS;EACd,MAAM,MAAM,IAAI,aAAa,QAAQ;EAErC,SAAS,kBAAkB,MAAiC;AAC1D,OAAI,CAAC,IAAI,cAAc,KAAK,CAAE;AAE9B,QAAK,MAAM,QAAQ,KAAK,YAAY;AAClC,QAAI,KAAK,SAAS,cAAc,KAAK,SAAU;IAE/C,MAAM,MAAM,WAAW,KAAK,IAAI;AAChC,QAAI,QAAQ,KAAM;IAElB,MAAM,UAAU,kBAAkB;AAClC,QAAI,QACF,SAAQ,OAAO;KACb,MAAM,KAAK;KACX,WAAW;KACX,MAAM;MAAE,QAAQ;MAAK,aAAa,QAAQ;MAAM;
|
|
1
|
+
{"version":3,"file":"prefer-shorthand-property.js","names":[],"sources":["../../src/rules/prefer-shorthand-property.ts"],"sourcesContent":["import type { TSESTree } from '@typescript-eslint/utils';\nimport { createRule } from '../create-rule.js';\nimport { TastyContext, styleObjectListeners } from '../context.js';\nimport { getKeyName } from '../utils.js';\nimport { SHORTHAND_MAPPING } from '../constants.js';\n\ntype MessageIds = 'preferShorthand';\n\nexport default createRule<[], MessageIds>({\n name: 'prefer-shorthand-property',\n meta: {\n type: 'suggestion',\n fixable: 'code',\n docs: {\n description:\n 'Suggest tasty shorthand when a native CSS property with a tasty alternative is used',\n },\n messages: {\n preferShorthand:\n \"Prefer tasty shorthand '{{alternative}}' instead of '{{native}}'.\",\n },\n schema: [],\n },\n defaultOptions: [],\n create(context) {\n const ctx = new TastyContext(context);\n\n function handleStyleObject(node: TSESTree.ObjectExpression) {\n if (!ctx.isStyleObject(node)) return;\n\n for (const prop of node.properties) {\n if (prop.type !== 'Property' || prop.computed) continue;\n\n const key = getKeyName(prop.key);\n if (key === null) continue;\n\n const mapping = SHORTHAND_MAPPING[key];\n if (mapping) {\n context.report({\n node: prop.key,\n messageId: 'preferShorthand',\n data: { native: key, alternative: mapping.hint },\n fix(fixer) {\n // Only auto-fix the carry-over subset where the value passes\n // through unchanged (e.g. backgroundColor → fill, borderRadius →\n // radius). Directional / min/max / border-* renames change\n // semantics and stay report-only.\n if (!mapping.safeFix) return null;\n return fixer.replaceText(prop.key, mapping.property);\n },\n });\n }\n }\n }\n\n return {\n ImportDeclaration(node) {\n ctx.trackImport(node);\n },\n ...styleObjectListeners(handleStyleObject),\n };\n },\n});\n"],"mappings":";;;;;;AAQA,wCAAe,WAA2B;CACxC,MAAM;CACN,MAAM;EACJ,MAAM;EACN,SAAS;EACT,MAAM,EACJ,aACE,uFACH;EACD,UAAU,EACR,iBACE,qEACH;EACD,QAAQ,EAAE;EACX;CACD,gBAAgB,EAAE;CAClB,OAAO,SAAS;EACd,MAAM,MAAM,IAAI,aAAa,QAAQ;EAErC,SAAS,kBAAkB,MAAiC;AAC1D,OAAI,CAAC,IAAI,cAAc,KAAK,CAAE;AAE9B,QAAK,MAAM,QAAQ,KAAK,YAAY;AAClC,QAAI,KAAK,SAAS,cAAc,KAAK,SAAU;IAE/C,MAAM,MAAM,WAAW,KAAK,IAAI;AAChC,QAAI,QAAQ,KAAM;IAElB,MAAM,UAAU,kBAAkB;AAClC,QAAI,QACF,SAAQ,OAAO;KACb,MAAM,KAAK;KACX,WAAW;KACX,MAAM;MAAE,QAAQ;MAAK,aAAa,QAAQ;MAAM;KAChD,IAAI,OAAO;AAKT,UAAI,CAAC,QAAQ,QAAS,QAAO;AAC7B,aAAO,MAAM,YAAY,KAAK,KAAK,QAAQ,SAAS;;KAEvD,CAAC;;;AAKR,SAAO;GACL,kBAAkB,MAAM;AACtB,QAAI,YAAY,KAAK;;GAEvB,GAAG,qBAAqB,kBAAkB;GAC3C;;CAEJ,CAAC"}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { createRule } from "../create-rule.js";
|
|
2
2
|
import { getKeyName } from "../utils.js";
|
|
3
3
|
import { TastyContext, styleObjectListeners } from "../context.js";
|
|
4
|
+
import { movePropertyToIndex, removePropertyWithComma } from "../fix-utils.js";
|
|
4
5
|
|
|
5
6
|
//#region src/rules/valid-default-state-order.ts
|
|
6
7
|
function getStateKeyName(key) {
|
|
@@ -12,9 +13,11 @@ var valid_default_state_order_default = createRule({
|
|
|
12
13
|
name: "valid-default-state-order",
|
|
13
14
|
meta: {
|
|
14
15
|
type: "suggestion",
|
|
15
|
-
|
|
16
|
+
fixable: "code",
|
|
17
|
+
docs: { description: "Warn when the default ('') state or '_' fallback floor is misplaced or redundant" },
|
|
16
18
|
messages: {
|
|
17
|
-
misplacedDefaultState: "Default state ('') in '{{property}}' must be the first key in the state map. Move it before other states — otherwise it overrides them at runtime.",
|
|
19
|
+
misplacedDefaultState: "Default state ('') in '{{property}}' must be the first key in the state map (or right after the '_' floor when present). Move it before other states — otherwise it overrides them at runtime.",
|
|
20
|
+
misplacedFloorState: "Fallback floor ('_') in '{{property}}' must be the first key in the state map, above the '' default. Move it to the top — it is the lowest-priority cascade layer.",
|
|
18
21
|
redundantDefaultState: "Default state ('') in '{{property}}' is redundant when '_' is the only other state. Remove '' — '_' already provides the always-on fallback value."
|
|
19
22
|
},
|
|
20
23
|
schema: []
|
|
@@ -39,14 +42,42 @@ var valid_default_state_order_default = createRule({
|
|
|
39
42
|
context.report({
|
|
40
43
|
node: stateKeys[defaultIndex].node.key,
|
|
41
44
|
messageId: "redundantDefaultState",
|
|
42
|
-
data: { property }
|
|
45
|
+
data: { property },
|
|
46
|
+
fix(fixer) {
|
|
47
|
+
return removePropertyWithComma(fixer, stateKeys[defaultIndex].node, context.sourceCode);
|
|
48
|
+
}
|
|
43
49
|
});
|
|
44
50
|
return;
|
|
45
51
|
}
|
|
46
|
-
if (
|
|
52
|
+
if (floorIndex !== -1 && floorIndex !== 0) {
|
|
53
|
+
context.report({
|
|
54
|
+
node: stateKeys[floorIndex].node.key,
|
|
55
|
+
messageId: "misplacedFloorState",
|
|
56
|
+
data: { property },
|
|
57
|
+
fix(fixer) {
|
|
58
|
+
return movePropertyToIndex(fixer, stateKeys[floorIndex].node, 0, context.sourceCode);
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
if (floorIndex === 0 && defaultIndex !== -1 && defaultIndex !== 1) {
|
|
64
|
+
context.report({
|
|
65
|
+
node: stateKeys[defaultIndex].node.key,
|
|
66
|
+
messageId: "misplacedDefaultState",
|
|
67
|
+
data: { property },
|
|
68
|
+
fix(fixer) {
|
|
69
|
+
return movePropertyToIndex(fixer, stateKeys[defaultIndex].node, 1, context.sourceCode);
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
if (floorIndex === -1 && defaultIndex > 0) context.report({
|
|
47
75
|
node: stateKeys[defaultIndex].node.key,
|
|
48
76
|
messageId: "misplacedDefaultState",
|
|
49
|
-
data: { property }
|
|
77
|
+
data: { property },
|
|
78
|
+
fix(fixer) {
|
|
79
|
+
return movePropertyToIndex(fixer, stateKeys[defaultIndex].node, 0, context.sourceCode);
|
|
80
|
+
}
|
|
50
81
|
});
|
|
51
82
|
}
|
|
52
83
|
function handleStyleObject(node) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"valid-default-state-order.js","names":[],"sources":["../../src/rules/valid-default-state-order.ts"],"sourcesContent":["import type { TSESTree } from '@typescript-eslint/utils';\nimport { createRule } from '../create-rule.js';\nimport { TastyContext, styleObjectListeners } from '../context.js';\nimport { getKeyName } from '../utils.js';\n\ntype MessageIds
|
|
1
|
+
{"version":3,"file":"valid-default-state-order.js","names":[],"sources":["../../src/rules/valid-default-state-order.ts"],"sourcesContent":["import type { TSESTree } from '@typescript-eslint/utils';\nimport { createRule } from '../create-rule.js';\nimport { TastyContext, styleObjectListeners } from '../context.js';\nimport { getKeyName } from '../utils.js';\nimport { removePropertyWithComma, movePropertyToIndex } from '../fix-utils.js';\n\ntype MessageIds =\n | 'misplacedDefaultState'\n | 'misplacedFloorState'\n | 'redundantDefaultState';\n\nfunction getStateKeyName(key: TSESTree.Node): string | null {\n if (key.type === 'Identifier') return key.name;\n if (key.type === 'Literal' && typeof key.value === 'string') return key.value;\n return null;\n}\n\nexport default createRule<[], MessageIds>({\n name: 'valid-default-state-order',\n meta: {\n type: 'suggestion',\n fixable: 'code',\n docs: {\n description:\n \"Warn when the default ('') state or '_' fallback floor is misplaced or redundant\",\n },\n messages: {\n misplacedDefaultState:\n \"Default state ('') in '{{property}}' must be the first key in the state map (or right after the '_' floor when present). Move it before other states — otherwise it overrides them at runtime.\",\n misplacedFloorState:\n \"Fallback floor ('_') in '{{property}}' must be the first key in the state map, above the '' default. Move it to the top — it is the lowest-priority cascade layer.\",\n redundantDefaultState:\n \"Default state ('') in '{{property}}' is redundant when '_' is the only other state. Remove '' — '_' already provides the always-on fallback value.\",\n },\n schema: [],\n },\n defaultOptions: [],\n create(context) {\n const ctx = new TastyContext(context);\n\n function checkStateMap(\n property: string,\n stateMap: TSESTree.ObjectExpression,\n ): void {\n const stateKeys: {\n key: string;\n node: TSESTree.Property;\n }[] = [];\n\n for (const prop of stateMap.properties) {\n if (prop.type !== 'Property' || prop.computed) continue;\n const stateKey = getStateKeyName(prop.key);\n if (stateKey === null) continue;\n stateKeys.push({ key: stateKey, node: prop });\n }\n\n const defaultIndex = stateKeys.findIndex((entry) => entry.key === '');\n const floorIndex = stateKeys.findIndex((entry) => entry.key === '_');\n\n // 1. Both '' and '_' present with no other states → '' is redundant.\n if (defaultIndex !== -1 && floorIndex !== -1 && stateKeys.length === 2) {\n context.report({\n node: stateKeys[defaultIndex].node.key,\n messageId: 'redundantDefaultState',\n data: { property },\n fix(fixer) {\n return removePropertyWithComma(\n fixer,\n stateKeys[defaultIndex].node,\n context.sourceCode,\n );\n },\n });\n return;\n }\n\n // 2. '_' present but not first → move it to the top. Re-evaluated next\n // pass so a misplaced '' is handled afterwards.\n if (floorIndex !== -1 && floorIndex !== 0) {\n context.report({\n node: stateKeys[floorIndex].node.key,\n messageId: 'misplacedFloorState',\n data: { property },\n fix(fixer) {\n return movePropertyToIndex(\n fixer,\n stateKeys[floorIndex].node,\n 0,\n context.sourceCode,\n );\n },\n });\n return;\n }\n\n // 3. '_' at top, '' present but not right after it → move '' to index 1.\n if (floorIndex === 0 && defaultIndex !== -1 && defaultIndex !== 1) {\n context.report({\n node: stateKeys[defaultIndex].node.key,\n messageId: 'misplacedDefaultState',\n data: { property },\n fix(fixer) {\n return movePropertyToIndex(\n fixer,\n stateKeys[defaultIndex].node,\n 1,\n context.sourceCode,\n );\n },\n });\n return;\n }\n\n // 4. No '_', '' not first → move '' to the top.\n if (floorIndex === -1 && defaultIndex > 0) {\n context.report({\n node: stateKeys[defaultIndex].node.key,\n messageId: 'misplacedDefaultState',\n data: { property },\n fix(fixer) {\n return movePropertyToIndex(\n fixer,\n stateKeys[defaultIndex].node,\n 0,\n context.sourceCode,\n );\n },\n });\n }\n }\n\n function handleStyleObject(node: TSESTree.ObjectExpression) {\n if (!ctx.isStyleObject(node)) return;\n\n for (const prop of node.properties) {\n if (prop.type !== 'Property' || prop.computed) continue;\n\n const key = getKeyName(prop.key);\n if (key === null) continue;\n if (/^[A-Z@&$#]/.test(key)) continue;\n\n if (prop.value.type !== 'ObjectExpression') continue;\n if (!ctx.isStateMap(prop.value, prop)) continue;\n\n checkStateMap(key, prop.value);\n }\n }\n\n return {\n ImportDeclaration(node) {\n ctx.trackImport(node);\n },\n ...styleObjectListeners(handleStyleObject),\n };\n },\n});\n"],"mappings":";;;;;;AAWA,SAAS,gBAAgB,KAAmC;AAC1D,KAAI,IAAI,SAAS,aAAc,QAAO,IAAI;AAC1C,KAAI,IAAI,SAAS,aAAa,OAAO,IAAI,UAAU,SAAU,QAAO,IAAI;AACxE,QAAO;;AAGT,wCAAe,WAA2B;CACxC,MAAM;CACN,MAAM;EACJ,MAAM;EACN,SAAS;EACT,MAAM,EACJ,aACE,oFACH;EACD,UAAU;GACR,uBACE;GACF,qBACE;GACF,uBACE;GACH;EACD,QAAQ,EAAE;EACX;CACD,gBAAgB,EAAE;CAClB,OAAO,SAAS;EACd,MAAM,MAAM,IAAI,aAAa,QAAQ;EAErC,SAAS,cACP,UACA,UACM;GACN,MAAM,YAGA,EAAE;AAER,QAAK,MAAM,QAAQ,SAAS,YAAY;AACtC,QAAI,KAAK,SAAS,cAAc,KAAK,SAAU;IAC/C,MAAM,WAAW,gBAAgB,KAAK,IAAI;AAC1C,QAAI,aAAa,KAAM;AACvB,cAAU,KAAK;KAAE,KAAK;KAAU,MAAM;KAAM,CAAC;;GAG/C,MAAM,eAAe,UAAU,WAAW,UAAU,MAAM,QAAQ,GAAG;GACrE,MAAM,aAAa,UAAU,WAAW,UAAU,MAAM,QAAQ,IAAI;AAGpE,OAAI,iBAAiB,MAAM,eAAe,MAAM,UAAU,WAAW,GAAG;AACtE,YAAQ,OAAO;KACb,MAAM,UAAU,cAAc,KAAK;KACnC,WAAW;KACX,MAAM,EAAE,UAAU;KAClB,IAAI,OAAO;AACT,aAAO,wBACL,OACA,UAAU,cAAc,MACxB,QAAQ,WACT;;KAEJ,CAAC;AACF;;AAKF,OAAI,eAAe,MAAM,eAAe,GAAG;AACzC,YAAQ,OAAO;KACb,MAAM,UAAU,YAAY,KAAK;KACjC,WAAW;KACX,MAAM,EAAE,UAAU;KAClB,IAAI,OAAO;AACT,aAAO,oBACL,OACA,UAAU,YAAY,MACtB,GACA,QAAQ,WACT;;KAEJ,CAAC;AACF;;AAIF,OAAI,eAAe,KAAK,iBAAiB,MAAM,iBAAiB,GAAG;AACjE,YAAQ,OAAO;KACb,MAAM,UAAU,cAAc,KAAK;KACnC,WAAW;KACX,MAAM,EAAE,UAAU;KAClB,IAAI,OAAO;AACT,aAAO,oBACL,OACA,UAAU,cAAc,MACxB,GACA,QAAQ,WACT;;KAEJ,CAAC;AACF;;AAIF,OAAI,eAAe,MAAM,eAAe,EACtC,SAAQ,OAAO;IACb,MAAM,UAAU,cAAc,KAAK;IACnC,WAAW;IACX,MAAM,EAAE,UAAU;IAClB,IAAI,OAAO;AACT,YAAO,oBACL,OACA,UAAU,cAAc,MACxB,GACA,QAAQ,WACT;;IAEJ,CAAC;;EAIN,SAAS,kBAAkB,MAAiC;AAC1D,OAAI,CAAC,IAAI,cAAc,KAAK,CAAE;AAE9B,QAAK,MAAM,QAAQ,KAAK,YAAY;AAClC,QAAI,KAAK,SAAS,cAAc,KAAK,SAAU;IAE/C,MAAM,MAAM,WAAW,KAAK,IAAI;AAChC,QAAI,QAAQ,KAAM;AAClB,QAAI,aAAa,KAAK,IAAI,CAAE;AAE5B,QAAI,KAAK,MAAM,SAAS,mBAAoB;AAC5C,QAAI,CAAC,IAAI,WAAW,KAAK,OAAO,KAAK,CAAE;AAEvC,kBAAc,KAAK,KAAK,MAAM;;;AAIlC,SAAO;GACL,kBAAkB,MAAM;AACtB,QAAI,YAAY,KAAK;;GAEvB,GAAG,qBAAqB,kBAAkB;GAC3C;;CAEJ,CAAC"}
|
|
@@ -2,6 +2,7 @@ import { createRule } from "../create-rule.js";
|
|
|
2
2
|
import { RADIUS_SHAPES } from "../constants.js";
|
|
3
3
|
import { getKeyName, getStringValue } from "../utils.js";
|
|
4
4
|
import { TastyContext, styleObjectListeners } from "../context.js";
|
|
5
|
+
import { replaceStringValue } from "../fix-utils.js";
|
|
5
6
|
|
|
6
7
|
//#region src/rules/valid-radius-shape.ts
|
|
7
8
|
const SHAPE_LIKE = /^[a-z]+$/;
|
|
@@ -9,8 +10,12 @@ var valid_radius_shape_default = createRule({
|
|
|
9
10
|
name: "valid-radius-shape",
|
|
10
11
|
meta: {
|
|
11
12
|
type: "problem",
|
|
13
|
+
hasSuggestions: true,
|
|
12
14
|
docs: { description: "Validate special shape keywords used with the radius property" },
|
|
13
|
-
messages: {
|
|
15
|
+
messages: {
|
|
16
|
+
unknownShape: "Unknown radius shape '{{shape}}'. Valid shapes: {{valid}}.",
|
|
17
|
+
replaceWithShape: "Replace '{{shape}}' with '{{suggestion}}'"
|
|
18
|
+
},
|
|
14
19
|
schema: []
|
|
15
20
|
},
|
|
16
21
|
defaultOptions: [],
|
|
@@ -34,13 +39,24 @@ var valid_radius_shape_default = createRule({
|
|
|
34
39
|
]).has(trimmed)) return;
|
|
35
40
|
const suggestion = findClosestShape(trimmed);
|
|
36
41
|
const validList = [...RADIUS_SHAPES].join(", ");
|
|
42
|
+
const suggest = suggestion ? [{
|
|
43
|
+
messageId: "replaceWithShape",
|
|
44
|
+
data: {
|
|
45
|
+
shape: trimmed,
|
|
46
|
+
suggestion
|
|
47
|
+
},
|
|
48
|
+
fix(fixer) {
|
|
49
|
+
return replaceStringValue(fixer, node, suggestion);
|
|
50
|
+
}
|
|
51
|
+
}] : void 0;
|
|
37
52
|
context.report({
|
|
38
53
|
node,
|
|
39
54
|
messageId: "unknownShape",
|
|
40
55
|
data: {
|
|
41
56
|
shape: trimmed,
|
|
42
57
|
valid: validList + (suggestion ? `. Did you mean '${suggestion}'?` : "")
|
|
43
|
-
}
|
|
58
|
+
},
|
|
59
|
+
suggest
|
|
44
60
|
});
|
|
45
61
|
}
|
|
46
62
|
function findClosestShape(input) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"valid-radius-shape.js","names":[],"sources":["../../src/rules/valid-radius-shape.ts"],"sourcesContent":["import type { TSESTree } from '@typescript-eslint/utils';\nimport { createRule } from '../create-rule.js';\nimport { TastyContext, styleObjectListeners } from '../context.js';\nimport { getKeyName, getStringValue } from '../utils.js';\nimport { RADIUS_SHAPES } from '../constants.js';\n\ntype MessageIds = 'unknownShape';\n\nconst SHAPE_LIKE = /^[a-z]+$/;\n\nexport default createRule<[], MessageIds>({\n name: 'valid-radius-shape',\n meta: {\n type: 'problem',\n docs: {\n description:\n 'Validate special shape keywords used with the radius property',\n },\n messages: {\n unknownShape:\n \"Unknown radius shape '{{shape}}'. Valid shapes: {{valid}}.\",\n },\n schema: [],\n },\n defaultOptions: [],\n create(context) {\n const ctx = new TastyContext(context);\n\n function checkRadiusValue(value: string, node: TSESTree.Node): void {\n const trimmed = value.trim();\n // Only check single-word values that look like keywords\n if (!SHAPE_LIKE.test(trimmed)) return;\n\n // Known valid keywords\n if (RADIUS_SHAPES.has(trimmed)) return;\n if (trimmed === 'true' || trimmed === 'false') return;\n if (trimmed === 'none' || trimmed === 'inherit' || trimmed === 'initial')\n return;\n\n // Check if it's a directional modifier (handled elsewhere)\n const directions = new Set([\n 'top',\n 'right',\n 'bottom',\n 'left',\n 'top-left',\n 'top-right',\n 'bottom-left',\n 'bottom-right',\n ]);\n if (directions.has(trimmed)) return;\n\n // It looks like they tried to use a shape keyword\n const suggestion = findClosestShape(trimmed);\n const validList = [...RADIUS_SHAPES].join(', ');\n\n context.report({\n node,\n messageId: 'unknownShape',\n data: {\n shape: trimmed,\n valid:\n validList + (suggestion ? `. Did you mean '${suggestion}'?` : ''),\n },\n });\n }\n\n function findClosestShape(input: string): string | null {\n for (const shape of RADIUS_SHAPES) {\n if (\n shape.startsWith(input.slice(0, 3)) ||\n input.startsWith(shape.slice(0, 3))\n ) {\n return shape;\n }\n }\n return null;\n }\n\n function handleStyleObject(node: TSESTree.ObjectExpression) {\n if (!ctx.isStyleObject(node)) return;\n\n for (const prop of node.properties) {\n if (prop.type !== 'Property' || prop.computed) continue;\n\n const key = getKeyName(prop.key);\n if (key !== 'radius') continue;\n\n const str = getStringValue(prop.value);\n if (str) {\n checkRadiusValue(str, prop.value);\n continue;\n }\n\n if (prop.value.type === 'ObjectExpression') {\n for (const stateProp of prop.value.properties) {\n if (stateProp.type !== 'Property') continue;\n const stateStr = getStringValue(stateProp.value);\n if (stateStr) {\n checkRadiusValue(stateStr, stateProp.value);\n }\n }\n }\n }\n }\n\n return {\n ImportDeclaration(node) {\n ctx.trackImport(node);\n },\n ...styleObjectListeners(handleStyleObject),\n };\n },\n});\n"],"mappings":"
|
|
1
|
+
{"version":3,"file":"valid-radius-shape.js","names":[],"sources":["../../src/rules/valid-radius-shape.ts"],"sourcesContent":["import type { TSESLint, TSESTree } from '@typescript-eslint/utils';\nimport { createRule } from '../create-rule.js';\nimport { TastyContext, styleObjectListeners } from '../context.js';\nimport { getKeyName, getStringValue } from '../utils.js';\nimport { RADIUS_SHAPES } from '../constants.js';\nimport { replaceStringValue } from '../fix-utils.js';\n\ntype MessageIds = 'unknownShape' | 'replaceWithShape';\n\nconst SHAPE_LIKE = /^[a-z]+$/;\n\nexport default createRule<[], MessageIds>({\n name: 'valid-radius-shape',\n meta: {\n type: 'problem',\n hasSuggestions: true,\n docs: {\n description:\n 'Validate special shape keywords used with the radius property',\n },\n messages: {\n unknownShape:\n \"Unknown radius shape '{{shape}}'. Valid shapes: {{valid}}.\",\n replaceWithShape: \"Replace '{{shape}}' with '{{suggestion}}'\",\n },\n schema: [],\n },\n defaultOptions: [],\n create(context) {\n const ctx = new TastyContext(context);\n\n function checkRadiusValue(value: string, node: TSESTree.Node): void {\n const trimmed = value.trim();\n // Only check single-word values that look like keywords\n if (!SHAPE_LIKE.test(trimmed)) return;\n\n // Known valid keywords\n if (RADIUS_SHAPES.has(trimmed)) return;\n if (trimmed === 'true' || trimmed === 'false') return;\n if (trimmed === 'none' || trimmed === 'inherit' || trimmed === 'initial')\n return;\n\n // Check if it's a directional modifier (handled elsewhere)\n const directions = new Set([\n 'top',\n 'right',\n 'bottom',\n 'left',\n 'top-left',\n 'top-right',\n 'bottom-left',\n 'bottom-right',\n ]);\n if (directions.has(trimmed)) return;\n\n // It looks like they tried to use a shape keyword\n const suggestion = findClosestShape(trimmed);\n const validList = [...RADIUS_SHAPES].join(', ');\n\n const suggest = suggestion\n ? [\n {\n messageId: 'replaceWithShape' as const,\n data: { shape: trimmed, suggestion },\n fix(fixer: TSESLint.RuleFixer) {\n return replaceStringValue(fixer, node, suggestion);\n },\n },\n ]\n : undefined;\n\n context.report({\n node,\n messageId: 'unknownShape',\n data: {\n shape: trimmed,\n valid:\n validList + (suggestion ? `. Did you mean '${suggestion}'?` : ''),\n },\n suggest,\n });\n }\n\n function findClosestShape(input: string): string | null {\n for (const shape of RADIUS_SHAPES) {\n if (\n shape.startsWith(input.slice(0, 3)) ||\n input.startsWith(shape.slice(0, 3))\n ) {\n return shape;\n }\n }\n return null;\n }\n\n function handleStyleObject(node: TSESTree.ObjectExpression) {\n if (!ctx.isStyleObject(node)) return;\n\n for (const prop of node.properties) {\n if (prop.type !== 'Property' || prop.computed) continue;\n\n const key = getKeyName(prop.key);\n if (key !== 'radius') continue;\n\n const str = getStringValue(prop.value);\n if (str) {\n checkRadiusValue(str, prop.value);\n continue;\n }\n\n if (prop.value.type === 'ObjectExpression') {\n for (const stateProp of prop.value.properties) {\n if (stateProp.type !== 'Property') continue;\n const stateStr = getStringValue(stateProp.value);\n if (stateStr) {\n checkRadiusValue(stateStr, stateProp.value);\n }\n }\n }\n }\n }\n\n return {\n ImportDeclaration(node) {\n ctx.trackImport(node);\n },\n ...styleObjectListeners(handleStyleObject),\n };\n },\n});\n"],"mappings":";;;;;;;AASA,MAAM,aAAa;AAEnB,iCAAe,WAA2B;CACxC,MAAM;CACN,MAAM;EACJ,MAAM;EACN,gBAAgB;EAChB,MAAM,EACJ,aACE,iEACH;EACD,UAAU;GACR,cACE;GACF,kBAAkB;GACnB;EACD,QAAQ,EAAE;EACX;CACD,gBAAgB,EAAE;CAClB,OAAO,SAAS;EACd,MAAM,MAAM,IAAI,aAAa,QAAQ;EAErC,SAAS,iBAAiB,OAAe,MAA2B;GAClE,MAAM,UAAU,MAAM,MAAM;AAE5B,OAAI,CAAC,WAAW,KAAK,QAAQ,CAAE;AAG/B,OAAI,cAAc,IAAI,QAAQ,CAAE;AAChC,OAAI,YAAY,UAAU,YAAY,QAAS;AAC/C,OAAI,YAAY,UAAU,YAAY,aAAa,YAAY,UAC7D;AAaF,OAVmB,IAAI,IAAI;IACzB;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACD,CAAC,CACa,IAAI,QAAQ,CAAE;GAG7B,MAAM,aAAa,iBAAiB,QAAQ;GAC5C,MAAM,YAAY,CAAC,GAAG,cAAc,CAAC,KAAK,KAAK;GAE/C,MAAM,UAAU,aACZ,CACE;IACE,WAAW;IACX,MAAM;KAAE,OAAO;KAAS;KAAY;IACpC,IAAI,OAA2B;AAC7B,YAAO,mBAAmB,OAAO,MAAM,WAAW;;IAErD,CACF,GACD;AAEJ,WAAQ,OAAO;IACb;IACA,WAAW;IACX,MAAM;KACJ,OAAO;KACP,OACE,aAAa,aAAa,mBAAmB,WAAW,MAAM;KACjE;IACD;IACD,CAAC;;EAGJ,SAAS,iBAAiB,OAA8B;AACtD,QAAK,MAAM,SAAS,cAClB,KACE,MAAM,WAAW,MAAM,MAAM,GAAG,EAAE,CAAC,IACnC,MAAM,WAAW,MAAM,MAAM,GAAG,EAAE,CAAC,CAEnC,QAAO;AAGX,UAAO;;EAGT,SAAS,kBAAkB,MAAiC;AAC1D,OAAI,CAAC,IAAI,cAAc,KAAK,CAAE;AAE9B,QAAK,MAAM,QAAQ,KAAK,YAAY;AAClC,QAAI,KAAK,SAAS,cAAc,KAAK,SAAU;AAG/C,QADY,WAAW,KAAK,IAAI,KACpB,SAAU;IAEtB,MAAM,MAAM,eAAe,KAAK,MAAM;AACtC,QAAI,KAAK;AACP,sBAAiB,KAAK,KAAK,MAAM;AACjC;;AAGF,QAAI,KAAK,MAAM,SAAS,mBACtB,MAAK,MAAM,aAAa,KAAK,MAAM,YAAY;AAC7C,SAAI,UAAU,SAAS,WAAY;KACnC,MAAM,WAAW,eAAe,UAAU,MAAM;AAChD,SAAI,SACF,kBAAiB,UAAU,UAAU,MAAM;;;;AAOrD,SAAO;GACL,kBAAkB,MAAM;AACtB,QAAI,YAAY,KAAK;;GAEvB,GAAG,qBAAqB,kBAAkB;GAC3C;;CAEJ,CAAC"}
|
|
@@ -2,16 +2,19 @@ import { createRule } from "../create-rule.js";
|
|
|
2
2
|
import { KNOWN_CSS_PROPERTIES, SEMANTIC_TRANSITIONS, TRANSITION_SEMANTIC_MAPPING } from "../constants.js";
|
|
3
3
|
import { getKeyName, getStringValue } from "../utils.js";
|
|
4
4
|
import { TastyContext, styleObjectListeners } from "../context.js";
|
|
5
|
+
import { replaceInStringValue } from "../fix-utils.js";
|
|
5
6
|
|
|
6
7
|
//#region src/rules/valid-transition.ts
|
|
7
8
|
var valid_transition_default = createRule({
|
|
8
9
|
name: "valid-transition",
|
|
9
10
|
meta: {
|
|
10
11
|
type: "suggestion",
|
|
12
|
+
hasSuggestions: true,
|
|
11
13
|
docs: { description: "Validate transition property values use valid semantic transition names" },
|
|
12
14
|
messages: {
|
|
13
15
|
unknownTransition: "Unknown transition name '{{name}}'. Use a semantic name ({{known}}) or a CSS property name.",
|
|
14
|
-
preferSemanticTransition: "Transition '{{native}}' has a Tasty semantic equivalent — use '{{semantic}}' instead."
|
|
16
|
+
preferSemanticTransition: "Transition '{{native}}' has a Tasty semantic equivalent — use '{{semantic}}' instead.",
|
|
17
|
+
useSemantic: "Use '{{semantic}}' instead of '{{native}}'"
|
|
15
18
|
},
|
|
16
19
|
schema: []
|
|
17
20
|
},
|
|
@@ -20,22 +23,50 @@ var valid_transition_default = createRule({
|
|
|
20
23
|
const ctx = new TastyContext(context);
|
|
21
24
|
function checkTransitionValue(value, node) {
|
|
22
25
|
const groups = value.split(",");
|
|
26
|
+
let offset = 0;
|
|
23
27
|
for (const group of groups) {
|
|
24
28
|
const parts = group.trim().split(/\s+/);
|
|
25
|
-
if (parts.length === 0)
|
|
29
|
+
if (parts.length === 0 || parts[0] === "") {
|
|
30
|
+
offset += group.length + 1;
|
|
31
|
+
continue;
|
|
32
|
+
}
|
|
26
33
|
const name = parts[0];
|
|
27
|
-
if (name.startsWith("$$"))
|
|
28
|
-
|
|
34
|
+
if (name.startsWith("$$")) {
|
|
35
|
+
offset += group.length + 1;
|
|
36
|
+
continue;
|
|
37
|
+
}
|
|
38
|
+
if (name.startsWith("##")) {
|
|
39
|
+
offset += group.length + 1;
|
|
40
|
+
continue;
|
|
41
|
+
}
|
|
29
42
|
const semantic = TRANSITION_SEMANTIC_MAPPING[name];
|
|
30
43
|
if (semantic) {
|
|
44
|
+
const nameStartInGroup = group.indexOf(name);
|
|
45
|
+
const start = offset + nameStartInGroup;
|
|
46
|
+
const end = start + name.length;
|
|
31
47
|
context.report({
|
|
32
48
|
node,
|
|
33
49
|
messageId: "preferSemanticTransition",
|
|
34
50
|
data: {
|
|
35
51
|
native: name,
|
|
36
52
|
semantic
|
|
37
|
-
}
|
|
53
|
+
},
|
|
54
|
+
suggest: [{
|
|
55
|
+
messageId: "useSemantic",
|
|
56
|
+
data: {
|
|
57
|
+
native: name,
|
|
58
|
+
semantic
|
|
59
|
+
},
|
|
60
|
+
fix(fixer) {
|
|
61
|
+
return replaceInStringValue(fixer, node, [{
|
|
62
|
+
start,
|
|
63
|
+
end,
|
|
64
|
+
replacement: semantic
|
|
65
|
+
}], context.sourceCode);
|
|
66
|
+
}
|
|
67
|
+
}]
|
|
38
68
|
});
|
|
69
|
+
offset += group.length + 1;
|
|
39
70
|
continue;
|
|
40
71
|
}
|
|
41
72
|
if (!SEMANTIC_TRANSITIONS.has(name) && !KNOWN_CSS_PROPERTIES.has(name) && name !== "all" && name !== "none") context.report({
|
|
@@ -46,6 +77,7 @@ var valid_transition_default = createRule({
|
|
|
46
77
|
known: [...SEMANTIC_TRANSITIONS].join(", ")
|
|
47
78
|
}
|
|
48
79
|
});
|
|
80
|
+
offset += group.length + 1;
|
|
49
81
|
}
|
|
50
82
|
}
|
|
51
83
|
function handleStyleObject(node) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"valid-transition.js","names":[],"sources":["../../src/rules/valid-transition.ts"],"sourcesContent":["import type { TSESTree } from '@typescript-eslint/utils';\nimport { createRule } from '../create-rule.js';\nimport { TastyContext, styleObjectListeners } from '../context.js';\nimport { getKeyName, getStringValue } from '../utils.js';\nimport {\n SEMANTIC_TRANSITIONS,\n KNOWN_CSS_PROPERTIES,\n TRANSITION_SEMANTIC_MAPPING,\n} from '../constants.js';\n\ntype MessageIds
|
|
1
|
+
{"version":3,"file":"valid-transition.js","names":[],"sources":["../../src/rules/valid-transition.ts"],"sourcesContent":["import type { TSESTree } from '@typescript-eslint/utils';\nimport { createRule } from '../create-rule.js';\nimport { TastyContext, styleObjectListeners } from '../context.js';\nimport { getKeyName, getStringValue } from '../utils.js';\nimport {\n SEMANTIC_TRANSITIONS,\n KNOWN_CSS_PROPERTIES,\n TRANSITION_SEMANTIC_MAPPING,\n} from '../constants.js';\nimport { replaceInStringValue } from '../fix-utils.js';\n\ntype MessageIds =\n | 'unknownTransition'\n | 'preferSemanticTransition'\n | 'useSemantic';\n\nexport default createRule<[], MessageIds>({\n name: 'valid-transition',\n meta: {\n type: 'suggestion',\n hasSuggestions: true,\n docs: {\n description:\n 'Validate transition property values use valid semantic transition names',\n },\n messages: {\n unknownTransition:\n \"Unknown transition name '{{name}}'. Use a semantic name ({{known}}) or a CSS property name.\",\n preferSemanticTransition:\n \"Transition '{{native}}' has a Tasty semantic equivalent — use '{{semantic}}' instead.\",\n useSemantic: \"Use '{{semantic}}' instead of '{{native}}'\",\n },\n schema: [],\n },\n defaultOptions: [],\n create(context) {\n const ctx = new TastyContext(context);\n\n function checkTransitionValue(value: string, node: TSESTree.Node): void {\n const groups = value.split(',');\n let offset = 0;\n\n for (const group of groups) {\n const parts = group.trim().split(/\\s+/);\n if (parts.length === 0 || parts[0] === '') {\n offset += group.length + 1;\n continue;\n }\n\n const name = parts[0];\n\n // $$ prefix is always valid (custom property reference: $$name -> --name)\n if (name.startsWith('$$')) {\n offset += group.length + 1;\n continue;\n }\n\n // ## prefix is always valid (color property reference: ##name -> --name-color)\n if (name.startsWith('##')) {\n offset += group.length + 1;\n continue;\n }\n\n const semantic = TRANSITION_SEMANTIC_MAPPING[name];\n if (semantic) {\n const nameStartInGroup = group.indexOf(name);\n const start = offset + nameStartInGroup;\n const end = start + name.length;\n context.report({\n node,\n messageId: 'preferSemanticTransition',\n data: { native: name, semantic },\n suggest: [\n {\n messageId: 'useSemantic',\n data: { native: name, semantic },\n fix(fixer) {\n return replaceInStringValue(\n fixer,\n node,\n [{ start, end, replacement: semantic }],\n context.sourceCode,\n );\n },\n },\n ],\n });\n offset += group.length + 1;\n continue;\n }\n\n if (\n !SEMANTIC_TRANSITIONS.has(name) &&\n !KNOWN_CSS_PROPERTIES.has(name) &&\n name !== 'all' &&\n name !== 'none'\n ) {\n context.report({\n node,\n messageId: 'unknownTransition',\n data: {\n name,\n known: [...SEMANTIC_TRANSITIONS].join(', '),\n },\n });\n }\n\n offset += group.length + 1;\n }\n }\n\n function handleStyleObject(node: TSESTree.ObjectExpression) {\n if (!ctx.isStyleObject(node)) return;\n\n for (const prop of node.properties) {\n if (prop.type !== 'Property' || prop.computed) continue;\n\n const key = getKeyName(prop.key);\n if (key !== 'transition') continue;\n\n const str = getStringValue(prop.value);\n if (str) {\n checkTransitionValue(str, prop.value);\n continue;\n }\n\n if (prop.value.type === 'ObjectExpression') {\n for (const stateProp of prop.value.properties) {\n if (stateProp.type !== 'Property') continue;\n const stateStr = getStringValue(stateProp.value);\n if (stateStr) {\n checkTransitionValue(stateStr, stateProp.value);\n }\n }\n }\n }\n }\n\n return {\n ImportDeclaration(node) {\n ctx.trackImport(node);\n },\n ...styleObjectListeners(handleStyleObject),\n };\n },\n});\n"],"mappings":";;;;;;;AAgBA,+BAAe,WAA2B;CACxC,MAAM;CACN,MAAM;EACJ,MAAM;EACN,gBAAgB;EAChB,MAAM,EACJ,aACE,2EACH;EACD,UAAU;GACR,mBACE;GACF,0BACE;GACF,aAAa;GACd;EACD,QAAQ,EAAE;EACX;CACD,gBAAgB,EAAE;CAClB,OAAO,SAAS;EACd,MAAM,MAAM,IAAI,aAAa,QAAQ;EAErC,SAAS,qBAAqB,OAAe,MAA2B;GACtE,MAAM,SAAS,MAAM,MAAM,IAAI;GAC/B,IAAI,SAAS;AAEb,QAAK,MAAM,SAAS,QAAQ;IAC1B,MAAM,QAAQ,MAAM,MAAM,CAAC,MAAM,MAAM;AACvC,QAAI,MAAM,WAAW,KAAK,MAAM,OAAO,IAAI;AACzC,eAAU,MAAM,SAAS;AACzB;;IAGF,MAAM,OAAO,MAAM;AAGnB,QAAI,KAAK,WAAW,KAAK,EAAE;AACzB,eAAU,MAAM,SAAS;AACzB;;AAIF,QAAI,KAAK,WAAW,KAAK,EAAE;AACzB,eAAU,MAAM,SAAS;AACzB;;IAGF,MAAM,WAAW,4BAA4B;AAC7C,QAAI,UAAU;KACZ,MAAM,mBAAmB,MAAM,QAAQ,KAAK;KAC5C,MAAM,QAAQ,SAAS;KACvB,MAAM,MAAM,QAAQ,KAAK;AACzB,aAAQ,OAAO;MACb;MACA,WAAW;MACX,MAAM;OAAE,QAAQ;OAAM;OAAU;MAChC,SAAS,CACP;OACE,WAAW;OACX,MAAM;QAAE,QAAQ;QAAM;QAAU;OAChC,IAAI,OAAO;AACT,eAAO,qBACL,OACA,MACA,CAAC;SAAE;SAAO;SAAK,aAAa;SAAU,CAAC,EACvC,QAAQ,WACT;;OAEJ,CACF;MACF,CAAC;AACF,eAAU,MAAM,SAAS;AACzB;;AAGF,QACE,CAAC,qBAAqB,IAAI,KAAK,IAC/B,CAAC,qBAAqB,IAAI,KAAK,IAC/B,SAAS,SACT,SAAS,OAET,SAAQ,OAAO;KACb;KACA,WAAW;KACX,MAAM;MACJ;MACA,OAAO,CAAC,GAAG,qBAAqB,CAAC,KAAK,KAAK;MAC5C;KACF,CAAC;AAGJ,cAAU,MAAM,SAAS;;;EAI7B,SAAS,kBAAkB,MAAiC;AAC1D,OAAI,CAAC,IAAI,cAAc,KAAK,CAAE;AAE9B,QAAK,MAAM,QAAQ,KAAK,YAAY;AAClC,QAAI,KAAK,SAAS,cAAc,KAAK,SAAU;AAG/C,QADY,WAAW,KAAK,IAAI,KACpB,aAAc;IAE1B,MAAM,MAAM,eAAe,KAAK,MAAM;AACtC,QAAI,KAAK;AACP,0BAAqB,KAAK,KAAK,MAAM;AACrC;;AAGF,QAAI,KAAK,MAAM,SAAS,mBACtB,MAAK,MAAM,aAAa,KAAK,MAAM,YAAY;AAC7C,SAAI,UAAU,SAAS,WAAY;KACnC,MAAM,WAAW,eAAe,UAAU,MAAM;AAChD,SAAI,SACF,sBAAqB,UAAU,UAAU,MAAM;;;;AAOzD,SAAO;GACL,kBAAkB,MAAM;AACtB,QAAI,YAAY,KAAK;;GAEvB,GAAG,qBAAqB,kBAAkB;GAC3C;;CAEJ,CAAC"}
|