@tenphi/eslint-plugin-tasty 1.7.3 → 1.8.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 +9 -1
- package/dist/configs.js +1 -0
- package/dist/configs.js.map +1 -1
- package/dist/constants.js +66 -25
- package/dist/constants.js.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/parsers/state-key-parser.js +24 -1
- package/dist/parsers/state-key-parser.js.map +1 -1
- package/dist/pseudo-elements.js +85 -0
- package/dist/pseudo-elements.js.map +1 -0
- package/dist/rules/no-ignored-pseudo-element-state.js +155 -0
- package/dist/rules/no-ignored-pseudo-element-state.js.map +1 -0
- package/dist/rules/no-state-in-selector.js +30 -23
- package/dist/rules/no-state-in-selector.js.map +1 -1
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"no-state-in-selector.js","names":[],"sources":["../../src/rules/no-state-in-selector.ts"],"sourcesContent":["import type { TSESTree } from '@typescript-eslint/utils';\nimport selectorParser from 'postcss-selector-parser';\nimport { createRule } from '../create-rule.js';\nimport { TastyContext, styleObjectListeners } from '../context.js';\nimport { getKeyName, getStringValue, unwrapExpression } from '../utils.js';\n\nconst STRUCTURAL_PSEUDOS = new Set([':is', ':where']);\
|
|
1
|
+
{"version":3,"file":"no-state-in-selector.js","names":[],"sources":["../../src/rules/no-state-in-selector.ts"],"sourcesContent":["import type { TSESTree } from '@typescript-eslint/utils';\nimport selectorParser from 'postcss-selector-parser';\nimport { createRule } from '../create-rule.js';\nimport { TastyContext, styleObjectListeners } from '../context.js';\nimport {\n isPseudoElement,\n pseudoElementMismatch,\n pseudoName,\n splitAtPseudoElement,\n} from '../pseudo-elements.js';\nimport { getKeyName, getStringValue, unwrapExpression } from '../utils.js';\n\nconst STRUCTURAL_PSEUDOS = new Set([':is', ':where']);\n\nfunction isCondition(node: selectorParser.Node): boolean {\n if (node.type === 'attribute') {\n // An exact data-element identity is structural, like Tasty's uppercase\n // element names. Other attributes express conditions on that element.\n return !(\n node.attribute === 'data-element' &&\n !node.namespace &&\n node.operator === '=' &&\n node.value\n );\n }\n if (node.type !== 'pseudo') return false;\n return !isPseudoElement(node) && !STRUCTURAL_PSEUDOS.has(pseudoName(node));\n}\n\nfunction findCondition(nodes: selectorParser.Node[]): string | null {\n for (const node of nodes) {\n if (isCondition(node)) return node.toString().trim();\n // Continue into structural wrappers: :where(img:hover) still contains\n // a condition, although the wrapper itself is structural.\n if (selectorParser.isContainer(node)) {\n const condition = findCondition(node.nodes);\n if (condition !== null) return condition;\n }\n }\n return null;\n}\n\n/** Whether @own() can express `node` on a pseudo-element. */\nfunction isPseudoElementState(node: selectorParser.Node): boolean {\n return node.type === 'pseudo' && !pseudoElementMismatch(node.toString());\n}\n\nfunction firstItemCondition(item: selectorParser.Selector): string | null {\n const split = splitAtPseudoElement(item);\n if (split === null) return findCondition(item.nodes);\n\n // The item ends in a pseudo-element, which Tasty treats as the subject:\n // @own() states go after the pseudo-element, and the states it cannot have\n // are dropped. Conditions on the originating element before it, and inside\n // it (::slotted(img:hover), ::after:hover::marker), must stay in `$`.\n return (\n findCondition(item.nodes.slice(0, split.compound)) ??\n findCondition(item.nodes.slice(split.last + 1).filter(isPseudoElementState))\n );\n}\n\nfunction firstStateCondition(selector: string): string | null {\n let root: selectorParser.Root;\n try {\n root = selectorParser().astSync(selector);\n } catch {\n // This recommendation is not a CSS syntax validator. Incomplete selectors\n // must not crash lint or produce a misleading state-map recommendation.\n return null;\n }\n\n // Each item of a selector list is judged on its own.\n for (const item of root.nodes) {\n const condition = firstItemCondition(item);\n if (condition !== null) return condition;\n }\n return null;\n}\n\nexport default createRule<[], 'stateInSelector'>({\n name: 'no-state-in-selector',\n meta: {\n type: 'suggestion',\n docs: {\n description:\n 'Keep sub-element selectors structural and express conditions in property state maps',\n },\n messages: {\n stateInSelector:\n \"Move state condition '{{condition}}' out of the sub-element '$' selector and into property state maps. Use @own(...) for the sub-element's own state. If the condition must stay in this selector, explicitly disable tasty/no-state-in-selector on this line with a reason.\",\n },\n schema: [],\n },\n defaultOptions: [],\n create(context) {\n const ctx = new TastyContext(context);\n const visited = new WeakSet<TSESTree.ObjectExpression>();\n\n function handleStyleObject(node: TSESTree.ObjectExpression) {\n if (visited.has(node) || !ctx.isStyleObject(node)) return;\n visited.add(node);\n const isSubElement = ctx.isInsideSubElement(node);\n\n for (const prop of node.properties) {\n if (prop.type !== 'Property') continue;\n const key = prop.computed\n ? getStringValue(unwrapExpression(prop.key))\n : getKeyName(prop.key);\n // Some listeners match only the shared/JSX root. Follow its owned\n // sub-elements explicitly, without entering state maps or metadata.\n if (!prop.computed && key && ctx.isSubElementKey(key)) {\n const child = unwrapExpression(prop.value);\n if (child.type === 'ObjectExpression') handleStyleObject(child);\n }\n if (!isSubElement || key !== '$') continue;\n\n const selector = getStringValue(unwrapExpression(prop.value));\n if (selector === null) continue;\n const condition = firstStateCondition(selector);\n if (condition === null) continue;\n\n context.report({\n node: prop.value,\n messageId: 'stateInSelector',\n data: { condition },\n });\n }\n }\n\n return {\n ImportDeclaration(node) {\n ctx.trackImport(node);\n },\n ...styleObjectListeners(handleStyleObject),\n };\n },\n});\n"],"mappings":";;;;;;;AAYA,MAAM,qBAAqB,IAAI,IAAI,CAAC,OAAO,SAAS,CAAC;AAErD,SAAS,YAAY,MAAoC;AACvD,KAAI,KAAK,SAAS,YAGhB,QAAO,EACL,KAAK,cAAc,kBACnB,CAAC,KAAK,aACN,KAAK,aAAa,OAClB,KAAK;AAGT,KAAI,KAAK,SAAS,SAAU,QAAO;AACnC,QAAO,CAAC,gBAAgB,KAAK,IAAI,CAAC,mBAAmB,IAAI,WAAW,KAAK,CAAC;;AAG5E,SAAS,cAAc,OAA6C;AAClE,MAAK,MAAM,QAAQ,OAAO;AACxB,MAAI,YAAY,KAAK,CAAE,QAAO,KAAK,UAAU,CAAC,MAAM;AAGpD,MAAI,eAAe,YAAY,KAAK,EAAE;GACpC,MAAM,YAAY,cAAc,KAAK,MAAM;AAC3C,OAAI,cAAc,KAAM,QAAO;;;AAGnC,QAAO;;;AAIT,SAAS,qBAAqB,MAAoC;AAChE,QAAO,KAAK,SAAS,YAAY,CAAC,sBAAsB,KAAK,UAAU,CAAC;;AAG1E,SAAS,mBAAmB,MAA8C;CACxE,MAAM,QAAQ,qBAAqB,KAAK;AACxC,KAAI,UAAU,KAAM,QAAO,cAAc,KAAK,MAAM;AAMpD,QACE,cAAc,KAAK,MAAM,MAAM,GAAG,MAAM,SAAS,CAAC,IAClD,cAAc,KAAK,MAAM,MAAM,MAAM,OAAO,EAAE,CAAC,OAAO,qBAAqB,CAAC;;AAIhF,SAAS,oBAAoB,UAAiC;CAC5D,IAAI;AACJ,KAAI;AACF,SAAO,gBAAgB,CAAC,QAAQ,SAAS;SACnC;AAGN,SAAO;;AAIT,MAAK,MAAM,QAAQ,KAAK,OAAO;EAC7B,MAAM,YAAY,mBAAmB,KAAK;AAC1C,MAAI,cAAc,KAAM,QAAO;;AAEjC,QAAO;;AAGT,mCAAe,WAAkC;CAC/C,MAAM;CACN,MAAM;EACJ,MAAM;EACN,MAAM,EACJ,aACE,uFACH;EACD,UAAU,EACR,iBACE,gRACH;EACD,QAAQ,EAAE;EACX;CACD,gBAAgB,EAAE;CAClB,OAAO,SAAS;EACd,MAAM,MAAM,IAAI,aAAa,QAAQ;EACrC,MAAM,0BAAU,IAAI,SAAoC;EAExD,SAAS,kBAAkB,MAAiC;AAC1D,OAAI,QAAQ,IAAI,KAAK,IAAI,CAAC,IAAI,cAAc,KAAK,CAAE;AACnD,WAAQ,IAAI,KAAK;GACjB,MAAM,eAAe,IAAI,mBAAmB,KAAK;AAEjD,QAAK,MAAM,QAAQ,KAAK,YAAY;AAClC,QAAI,KAAK,SAAS,WAAY;IAC9B,MAAM,MAAM,KAAK,WACb,eAAe,iBAAiB,KAAK,IAAI,CAAC,GAC1C,WAAW,KAAK,IAAI;AAGxB,QAAI,CAAC,KAAK,YAAY,OAAO,IAAI,gBAAgB,IAAI,EAAE;KACrD,MAAM,QAAQ,iBAAiB,KAAK,MAAM;AAC1C,SAAI,MAAM,SAAS,mBAAoB,mBAAkB,MAAM;;AAEjE,QAAI,CAAC,gBAAgB,QAAQ,IAAK;IAElC,MAAM,WAAW,eAAe,iBAAiB,KAAK,MAAM,CAAC;AAC7D,QAAI,aAAa,KAAM;IACvB,MAAM,YAAY,oBAAoB,SAAS;AAC/C,QAAI,cAAc,KAAM;AAExB,YAAQ,OAAO;KACb,MAAM,KAAK;KACX,WAAW;KACX,MAAM,EAAE,WAAW;KACpB,CAAC;;;AAIN,SAAO;GACL,kBAAkB,MAAM;AACtB,QAAI,YAAY,KAAK;;GAEvB,GAAG,qBAAqB,kBAAkB;GAC3C;;CAEJ,CAAC"}
|