diff --git a/package.json b/package.json index 1ecc552..06ab8a2 100644 --- a/package.json +++ b/package.json @@ -38,7 +38,6 @@ "dependencies": { "@typescript-eslint/utils": "^6.4.0", "is-html": "^2.0.0", - "jsx-ast-utils": "^3.3.3", "kebab-case": "^1.0.2", "known-css-properties": "^0.24.0", "style-to-object": "^0.3.0" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 6b1929f..aa0a685 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -11,9 +11,6 @@ dependencies: is-html: specifier: ^2.0.0 version: 2.0.0 - jsx-ast-utils: - specifier: ^3.3.3 - version: 3.3.3 kebab-case: specifier: ^1.0.2 version: 1.0.2 @@ -1553,6 +1550,7 @@ packages: dependencies: call-bind: 1.0.2 is-array-buffer: 3.0.2 + dev: true /array-includes@3.1.6: resolution: {integrity: sha512-sgTbLvL6cNnw24FnbaDyjmvddQ2ML8arZsgaJhoABMoplz/4QRhtrYS+alr1BUM1Bwp6dhx8vVCBSLG+StwOFw==} @@ -1563,6 +1561,7 @@ packages: es-abstract: 1.21.2 get-intrinsic: 1.2.0 is-string: 1.0.7 + dev: true /array-union@2.1.0: resolution: {integrity: sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==} @@ -1609,6 +1608,7 @@ packages: /available-typed-arrays@1.0.5: resolution: {integrity: sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==} engines: {node: '>= 0.4'} + dev: true /babel-jest@29.5.0(@babel/core@7.21.3): resolution: {integrity: sha512-mA4eCDh5mSo2EcA9xQjVTpmbbNk32Zb3Q3QFQsNhaK56Q+yoXowzFodLux30HRgyOho5rsQ6B0P9QpMkvvnJ0Q==} @@ -1738,6 +1738,7 @@ packages: dependencies: function-bind: 1.1.1 get-intrinsic: 1.2.0 + dev: true /callsites@3.1.0: resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} @@ -2002,6 +2003,7 @@ packages: dependencies: has-property-descriptors: 1.0.0 object-keys: 1.1.1 + dev: true /delayed-stream@1.0.0: resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} @@ -2158,6 +2160,7 @@ packages: typed-array-length: 1.0.4 unbox-primitive: 1.0.2 which-typed-array: 1.1.9 + dev: true /es-set-tostringtag@2.0.1: resolution: {integrity: sha512-g3OMbtlwY3QewlqAiMLI47KywjWZoEytKr8pf6iTC8uJq5bIAH52Z9pnQ8pVL6whrCto53JZDuUIsifGeLorTg==} @@ -2166,6 +2169,7 @@ packages: get-intrinsic: 1.2.0 has: 1.0.3 has-tostringtag: 1.0.0 + dev: true /es-shim-unscopables@1.0.0: resolution: {integrity: sha512-Jm6GPcCdC30eMLbZ2x8z2WuRwAws3zTBBKuusffYVUrNj/GVSUAZ+xKMaUpfNDR5IbyNA5LJbaecoUVbmUcB1w==} @@ -2180,6 +2184,7 @@ packages: is-callable: 1.2.7 is-date-object: 1.0.5 is-symbol: 1.0.4 + dev: true /escalade@3.1.1: resolution: {integrity: sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==} @@ -2694,6 +2699,7 @@ packages: resolution: {integrity: sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==} dependencies: is-callable: 1.2.7 + dev: true /form-data@2.5.1: resolution: {integrity: sha512-m21N3WOmEEURgk6B9GLOE4RuWOFf28Lhh9qGYeNlGq4VDXUlJy2th2slBNU8Gp8EzloYZOibZJ7t5ecIrFSjVA==} @@ -2731,6 +2737,7 @@ packages: /function-bind@1.1.1: resolution: {integrity: sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==} + dev: true /function.prototype.name@1.1.5: resolution: {integrity: sha512-uN7m/BzVKQnCUF/iW8jYea67v++2u7m5UgENbHRtdDVclOUP+FMPlCNdmk0h/ysGyo2tavMJEDqJAkJdRa1vMA==} @@ -2740,6 +2747,7 @@ packages: define-properties: 1.2.0 es-abstract: 1.21.2 functions-have-names: 1.2.3 + dev: true /functional-red-black-tree@1.0.1: resolution: {integrity: sha512-dsKNQNdj6xA3T+QlADDA7mOSlX0qiMINjn0cgr+eGHGsbSHzTabcIogz2+p/iqP1Xs6EP/sS2SbqH+brGTbq0g==} @@ -2747,6 +2755,7 @@ packages: /functions-have-names@1.2.3: resolution: {integrity: sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==} + dev: true /gensync@1.0.0-beta.2: resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} @@ -2764,6 +2773,7 @@ packages: function-bind: 1.1.1 has: 1.0.3 has-symbols: 1.0.3 + dev: true /get-own-enumerable-property-symbols@3.0.2: resolution: {integrity: sha512-I0UBV/XOz1XkIJHEUDMZAbzCThU/H8DxmSfmdGcKPnVhu2VfFqr34jr9777IyaTYvxjedWhqVIilEDsCdP5G6g==} @@ -2790,6 +2800,7 @@ packages: dependencies: call-bind: 1.0.2 get-intrinsic: 1.2.0 + dev: true /glob-parent@5.1.2: resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} @@ -2843,6 +2854,7 @@ packages: engines: {node: '>= 0.4'} dependencies: define-properties: 1.2.0 + dev: true /globby@10.0.2: resolution: {integrity: sha512-7dUi7RvCoT/xast/o/dLN53oqND4yk0nsHkhRgn9w65C4PofCLOoJ39iSOg+qVDdWQPIEj+eszMHQ+aLVwwQSg==} @@ -2873,6 +2885,7 @@ packages: resolution: {integrity: sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==} dependencies: get-intrinsic: 1.2.0 + dev: true /graceful-fs@4.2.11: resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} @@ -2883,6 +2896,7 @@ packages: /has-bigints@1.0.2: resolution: {integrity: sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==} + dev: true /has-flag@3.0.0: resolution: {integrity: sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==} @@ -2897,26 +2911,31 @@ packages: resolution: {integrity: sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==} dependencies: get-intrinsic: 1.2.0 + dev: true /has-proto@1.0.1: resolution: {integrity: sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==} engines: {node: '>= 0.4'} + dev: true /has-symbols@1.0.3: resolution: {integrity: sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==} engines: {node: '>= 0.4'} + dev: true /has-tostringtag@1.0.0: resolution: {integrity: sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==} engines: {node: '>= 0.4'} dependencies: has-symbols: 1.0.3 + dev: true /has@1.0.3: resolution: {integrity: sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==} engines: {node: '>= 0.4.0'} dependencies: function-bind: 1.1.1 + dev: true /html-escaper@2.0.2: resolution: {integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==} @@ -3041,6 +3060,7 @@ packages: get-intrinsic: 1.2.0 has: 1.0.3 side-channel: 1.0.4 + dev: true /is-alphabetical@1.0.4: resolution: {integrity: sha512-DwzsA04LQ10FHTZuL0/grVDk4rFoVH1pjAToYwBrHSxcrBIGQuXrQMtD5U1b0U2XVgKZCTLLP8u2Qxqhy3l2Vg==} @@ -3059,6 +3079,7 @@ packages: call-bind: 1.0.2 get-intrinsic: 1.2.0 is-typed-array: 1.1.10 + dev: true /is-arrayish@0.2.1: resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==} @@ -3068,6 +3089,7 @@ packages: resolution: {integrity: sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==} dependencies: has-bigints: 1.0.2 + dev: true /is-boolean-object@1.1.2: resolution: {integrity: sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==} @@ -3075,6 +3097,7 @@ packages: dependencies: call-bind: 1.0.2 has-tostringtag: 1.0.0 + dev: true /is-buffer@2.0.5: resolution: {integrity: sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ==} @@ -3091,6 +3114,7 @@ packages: /is-callable@1.2.7: resolution: {integrity: sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==} engines: {node: '>= 0.4'} + dev: true /is-core-module@2.11.0: resolution: {integrity: sha512-RRjxlvLDkD1YJwDbroBHMb+cukurkDWNyHx7D3oNB5x9rb5ogcksMC5wHCadcXoo67gVr/+3GFySh3134zi6rw==} @@ -3103,6 +3127,7 @@ packages: engines: {node: '>= 0.4'} dependencies: has-tostringtag: 1.0.0 + dev: true /is-decimal@1.0.4: resolution: {integrity: sha512-RGdriMmQQvZ2aqaQq3awNA6dCGtKpiDFcOzrTWrDAT2MiWrKQVPmxLGHl7Y2nNu6led0kEyoX0enY0qXYsv9zw==} @@ -3155,12 +3180,14 @@ packages: /is-negative-zero@2.0.2: resolution: {integrity: sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==} engines: {node: '>= 0.4'} + dev: true /is-number-object@1.0.7: resolution: {integrity: sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==} engines: {node: '>= 0.4'} dependencies: has-tostringtag: 1.0.0 + dev: true /is-number@7.0.0: resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} @@ -3192,6 +3219,7 @@ packages: dependencies: call-bind: 1.0.2 has-tostringtag: 1.0.0 + dev: true /is-regexp@1.0.0: resolution: {integrity: sha512-7zjFAPO4/gwyQAAgRRmqeEeyIICSdmCqa3tsVHMdBzaXXRiqopZL4Cyghg/XulGWrtABTpbnYYzzIRffLkP4oA==} @@ -3202,6 +3230,7 @@ packages: resolution: {integrity: sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==} dependencies: call-bind: 1.0.2 + dev: true /is-stream@2.0.1: resolution: {integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==} @@ -3213,12 +3242,14 @@ packages: engines: {node: '>= 0.4'} dependencies: has-tostringtag: 1.0.0 + dev: true /is-symbol@1.0.4: resolution: {integrity: sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==} engines: {node: '>= 0.4'} dependencies: has-symbols: 1.0.3 + dev: true /is-typed-array@1.1.10: resolution: {integrity: sha512-PJqgEHiWZvMpaFZ3uTc8kHPM4+4ADTlDniuQL7cU/UDA0Ql7F70yGfHph3cLNe+c9toaigv+DFzTJKhc2CtO6A==} @@ -3229,11 +3260,13 @@ packages: for-each: 0.3.3 gopd: 1.0.1 has-tostringtag: 1.0.0 + dev: true /is-weakref@1.0.2: resolution: {integrity: sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==} dependencies: call-bind: 1.0.2 + dev: true /isarray@1.0.0: resolution: {integrity: sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==} @@ -3787,14 +3820,6 @@ packages: graceful-fs: 4.2.11 dev: true - /jsx-ast-utils@3.3.3: - resolution: {integrity: sha512-fYQHZTZ8jSfmWZ0iyzfwiU4WDX4HpHbMCZ3gPlWYiCl3BoeOTsqKBqnTVfH2rYT7eP5c3sVbeSPHnnJOaTrWiw==} - engines: {node: '>=4.0'} - dependencies: - array-includes: 3.1.6 - object.assign: 4.1.4 - dev: false - /kebab-case@1.0.2: resolution: {integrity: sha512-7n6wXq4gNgBELfDCpzKc+mRrZFs7D+wgfF5WRFLNAr4DA/qtr9Js8uOAVAfHhuLMfAcQ0pRKqbpjx+TcJVdE1Q==} dev: false @@ -4226,10 +4251,12 @@ packages: /object-inspect@1.12.3: resolution: {integrity: sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==} + dev: true /object-keys@1.1.1: resolution: {integrity: sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==} engines: {node: '>= 0.4'} + dev: true /object.assign@4.1.4: resolution: {integrity: sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ==} @@ -4239,6 +4266,7 @@ packages: define-properties: 1.2.0 has-symbols: 1.0.3 object-keys: 1.1.1 + dev: true /object.values@1.1.6: resolution: {integrity: sha512-FVVTkD1vENCsAcwNs9k6jea2uHC/X0+JcjG8YA60FN5CMaJmG95wT9jek/xX9nornqGRrBkKtzuAu2wuHpKqvw==} @@ -4521,6 +4549,7 @@ packages: call-bind: 1.0.2 define-properties: 1.2.0 functions-have-names: 1.2.3 + dev: true /regexpp@2.0.1: resolution: {integrity: sha512-lv0M6+TkDVniA3aD1Eg0DVpfU/booSu7Eev3TDO/mZKHBfVjgCGTV4t4buppESEYDtkArYFOxTJWv6S5C+iaNw==} @@ -4677,6 +4706,7 @@ packages: call-bind: 1.0.2 get-intrinsic: 1.2.0 is-regex: 1.1.4 + dev: true /safer-buffer@2.1.2: resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} @@ -4742,6 +4772,7 @@ packages: call-bind: 1.0.2 get-intrinsic: 1.2.0 object-inspect: 1.12.3 + dev: true /signal-exit@3.0.7: resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} @@ -4847,6 +4878,7 @@ packages: call-bind: 1.0.2 define-properties: 1.2.0 es-abstract: 1.21.2 + dev: true /string.prototype.trimend@1.0.6: resolution: {integrity: sha512-JySq+4mrPf9EsDBEDYMOb/lM7XQLulwg5R/m1r0PXEFqrV0qHvl58sdTilSXtKOflCsK2E8jxf+GKC0T07RWwQ==} @@ -4854,6 +4886,7 @@ packages: call-bind: 1.0.2 define-properties: 1.2.0 es-abstract: 1.21.2 + dev: true /string.prototype.trimstart@1.0.6: resolution: {integrity: sha512-omqjMDaY92pbn5HOX7f9IccLA+U1tA9GvtU4JrodiXFfYB7jPzzHpRzpglLAjtUV6bB557zwClJezTqnAiYnQA==} @@ -4861,6 +4894,7 @@ packages: call-bind: 1.0.2 define-properties: 1.2.0 es-abstract: 1.21.2 + dev: true /string_decoder@1.1.1: resolution: {integrity: sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==} @@ -5166,6 +5200,7 @@ packages: call-bind: 1.0.2 for-each: 0.3.3 is-typed-array: 1.1.10 + dev: true /typedarray@0.0.6: resolution: {integrity: sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==} @@ -5183,6 +5218,7 @@ packages: has-bigints: 1.0.2 has-symbols: 1.0.3 which-boxed-primitive: 1.0.2 + dev: true /unified@9.2.2: resolution: {integrity: sha512-Sg7j110mtefBD+qunSLO1lqOEKdrwBFBrR6Qd8f4uwkhWNlbkaqwHse6e7QvD3AP/MNoJdEDLaf8OxYyoWgorQ==} @@ -5289,6 +5325,7 @@ packages: is-number-object: 1.0.7 is-string: 1.0.7 is-symbol: 1.0.4 + dev: true /which-typed-array@1.1.9: resolution: {integrity: sha512-w9c4xkx6mPidwp7180ckYWfMmvxpjlZuIudNtDf4N/tTAUB8VJbX25qZoAsrtGuYNnGw3pa0AXgbGKRB8/EceA==} @@ -5300,6 +5337,7 @@ packages: gopd: 1.0.1 has-tostringtag: 1.0.0 is-typed-array: 1.1.10 + dev: true /which@1.3.1: resolution: {integrity: sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==} diff --git a/src/deps.d.ts b/src/deps.d.ts index c7d080f..8d311a2 100644 --- a/src/deps.d.ts +++ b/src/deps.d.ts @@ -1,2 +1 @@ -declare module "jsx-ast-utils"; declare module "kebab-case"; diff --git a/src/rules/jsx-no-duplicate-props.ts b/src/rules/jsx-no-duplicate-props.ts index 01789f8..504f7d5 100644 --- a/src/rules/jsx-no-duplicate-props.ts +++ b/src/rules/jsx-no-duplicate-props.ts @@ -1,4 +1,5 @@ import { TSESTree as T, ESLintUtils } from "@typescript-eslint/utils"; +import { jsxGetAllProps } from "../utils"; const createRule = ESLintUtils.RuleCreator.withoutDocs; @@ -58,27 +59,9 @@ export default createRule({ props.add(name); }; - node.attributes.forEach((decl) => { - if (decl.type === "JSXSpreadAttribute") { - if (decl.argument.type === "ObjectExpression") { - for (const prop of decl.argument.properties) { - if (prop.type === "Property") { - if (prop.key.type === "Identifier") { - checkPropName(prop.key.name, prop.key); - } else if (prop.key.type === "Literal") { - checkPropName(String(prop.key.value), prop.key); - } - } - } - } - } else { - const name = - decl.name.type === "JSXNamespacedName" - ? `${decl.name.namespace.name}:${decl.name.name.name}` - : decl.name.name; - checkPropName(name, decl.name); - } - }); + for (const [name, propNode] of jsxGetAllProps(node.attributes)) { + checkPropName(name, propNode); + } const hasChildrenProp = props.has("children"); const hasChildren = (node.parent as T.JSXElement | T.JSXFragment).children.length > 0; diff --git a/src/rules/no-innerhtml.ts b/src/rules/no-innerhtml.ts index 41e17d2..b3b3e6e 100644 --- a/src/rules/no-innerhtml.ts +++ b/src/rules/no-innerhtml.ts @@ -1,6 +1,6 @@ import { ESLintUtils, ASTUtils } from "@typescript-eslint/utils"; -import { propName } from "jsx-ast-utils"; import isHtml from "is-html"; +import { jsxPropName } from "../utils"; const createRule = ESLintUtils.RuleCreator.withoutDocs; const { getStringIfConstant } = ASTUtils; @@ -48,7 +48,7 @@ export default createRule({ const allowStatic = Boolean(context.options[0]?.allowStatic ?? true); return { JSXAttribute(node) { - if (propName(node) === "dangerouslySetInnerHTML") { + if (jsxPropName(node) === "dangerouslySetInnerHTML") { if ( node.value?.type === "JSXExpressionContainer" && node.value.expression.type === "ObjectExpression" && @@ -85,7 +85,7 @@ export default createRule({ }); } return; - } else if (propName(node) !== "innerHTML") { + } else if (jsxPropName(node) !== "innerHTML") { return; } diff --git a/src/rules/no-react-specific-props.ts b/src/rules/no-react-specific-props.ts index 6001f00..b9e2ff0 100644 --- a/src/rules/no-react-specific-props.ts +++ b/src/rules/no-react-specific-props.ts @@ -1,6 +1,5 @@ import { TSESLint, ESLintUtils } from "@typescript-eslint/utils"; -import { getProp, hasProp } from "jsx-ast-utils"; -import { isDOMElementName } from "../utils"; +import { isDOMElementName, jsxGetProp, jsxHasProp } from "../utils"; const createRule = ESLintUtils.RuleCreator.withoutDocs; @@ -29,10 +28,10 @@ export default createRule({ return { JSXOpeningElement(node) { for (const { from, to } of reactSpecificProps) { - const classNameAttribute = getProp(node.attributes, from); + const classNameAttribute = jsxGetProp(node.attributes, from); if (classNameAttribute) { // only auto-fix if there is no class prop defined - const fix = !hasProp(node.attributes, to, { ignoreCase: false }) + const fix = !jsxHasProp(node.attributes, to) ? (fixer: TSESLint.RuleFixer) => fixer.replaceText(classNameAttribute.name, to) : undefined; @@ -45,7 +44,7 @@ export default createRule({ } } if (node.name.type === "JSXIdentifier" && isDOMElementName(node.name.name)) { - const keyProp = getProp(node.attributes, "key"); + const keyProp = jsxGetProp(node.attributes, "key"); if (keyProp) { // no DOM element has a 'key' prop, so we can assert that this is a holdover from React. context.report({ diff --git a/src/rules/prefer-classlist.ts b/src/rules/prefer-classlist.ts index 0667fb7..0d5fddc 100644 --- a/src/rules/prefer-classlist.ts +++ b/src/rules/prefer-classlist.ts @@ -1,5 +1,5 @@ import { ESLintUtils, TSESTree as T } from "@typescript-eslint/utils"; -import { hasProp, propName } from "jsx-ast-utils"; +import { jsxHasProp, jsxPropName } from "../utils"; const createRule = ESLintUtils.RuleCreator.withoutDocs; @@ -45,10 +45,11 @@ export default createRule({ return { JSXAttribute(node) { if ( - ["class", "className"].indexOf(propName(node)) === -1 || - hasProp((node.parent as T.JSXOpeningElement | undefined)?.attributes, "classlist", { - ignoreCase: false, - }) + ["class", "className"].indexOf(jsxPropName(node)) === -1 || + jsxHasProp( + (node.parent as T.JSXOpeningElement | undefined)?.attributes ?? [], + "classlist" + ) ) { return; } diff --git a/src/rules/style-prop.ts b/src/rules/style-prop.ts index c3ad5db..2e31584 100644 --- a/src/rules/style-prop.ts +++ b/src/rules/style-prop.ts @@ -2,7 +2,7 @@ import { TSESTree as T, ESLintUtils, ASTUtils } from "@typescript-eslint/utils"; import kebabCase from "kebab-case"; import { all as allCssProperties } from "known-css-properties"; import parse from "style-to-object"; -import { propName } from "jsx-ast-utils"; +import { jsxPropName } from "../utils"; const createRule = ESLintUtils.RuleCreator.withoutDocs; const { getPropertyName, getStaticValue } = ASTUtils; @@ -62,7 +62,7 @@ export default createRule({ return { JSXAttribute(node) { - if (styleProps.indexOf(propName(node)) === -1) { + if (styleProps.indexOf(jsxPropName(node)) === -1) { return; } const style = diff --git a/src/utils.ts b/src/utils.ts index 2983ae3..751b809 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -220,3 +220,47 @@ export function removeSpecifier( } return fixer.remove(specifier); } + +export function jsxPropName(prop: T.JSXAttribute) { + if (prop.name.type === "JSXNamespacedName") { + return `${prop.name.namespace.name}:${prop.name.name.name}`; + } + + return prop.name.name; +} + +type Props = T.JSXOpeningElement["attributes"]; + +/** Iterate through both attributes and spread object props, yielding the name and the node. */ +export function* jsxGetAllProps(props: Props): Generator<[string, T.Node]> { + for (const attr of props) { + if (attr.type === "JSXSpreadAttribute" && attr.argument.type === "ObjectExpression") { + for (const property of attr.argument.properties) { + if (property.type === "Property") { + if (property.key.type === "Identifier") { + yield [property.key.name, property.key]; + } else if (property.key.type === "Literal") { + yield [String(property.key.value), property.key]; + } + } + } + } else if (attr.type === "JSXAttribute") { + yield [jsxPropName(attr), attr.name]; + } + } +} + +/** Returns whether an element has a prop, checking spread object props. */ +export function jsxHasProp(props: Props, prop: string) { + for (const [p] of jsxGetAllProps(props)) { + if (p === prop) return true; + } + return false; +} + +/** Get a JSXAttribute, excluding spread props. */ +export function jsxGetProp(props: Props, prop: string) { + return props.find( + (attribute) => attribute.type !== "JSXSpreadAttribute" && prop === jsxPropName(attribute) + ) as T.JSXAttribute | undefined; +}