From c5464368de9b879fa39470ce8423f0844289aa93 Mon Sep 17 00:00:00 2001 From: Neptia Date: Tue, 19 May 2026 14:43:46 +0200 Subject: [PATCH 1/4] Add Vue support to avoid-autoplay rule and enhance documentation --- eslint-plugin/docs/rules/avoid-autoplay.md | 10 ++- eslint-plugin/lib/rules/avoid-autoplay.js | 77 +++++++++++++------ eslint-plugin/package.json | 3 +- .../tests/lib/rules/avoid-autoplay.test.js | 38 +++++++++ eslint-plugin/yarn.lock | 36 +++++++-- test-project/eslint.config.mjs | 12 +++ test-project/package.json | 4 +- test-project/src/{ => js}/avoid-autoplay.js | 0 .../src/{ => js}/avoid-brightness-override.js | 0 .../avoid-high-accuracy-geolocation.js | 0 .../avoid-keep-awake-react-native-fn.js | 0 .../avoid-keep-awake-react-native-hook.js | 0 .../src/{ => js}/import-all-from-library.js | 0 .../src/{ => js}/limit-db-query-results.js | 0 .../{ => js}/modular-import-from-library.js | 0 .../src/{ => js}/no-css-animations.js | 0 .../{ => js}/no-empty-image-src-attribute.js | 0 .../no-imported-number-format-library.js | 0 .../no-multiple-access-dom-element.js | 0 test-project/src/{ => js}/no-torch.js | 0 .../prefer-lighter-formats-for-image-files.js | 0 .../prefer-shorthand-css-notations.js | 0 .../src/{ => js}/provide-print-css.js | 0 .../rule-no-multiple-style-changes.js | 0 .../prefer-collections-with-pagination.ts | 0 test-project/src/vue/avoid-autoplay.vue | 4 + 26 files changed, 152 insertions(+), 32 deletions(-) rename test-project/src/{ => js}/avoid-autoplay.js (100%) rename test-project/src/{ => js}/avoid-brightness-override.js (100%) rename test-project/src/{ => js}/avoid-high-accuracy-geolocation.js (100%) rename test-project/src/{ => js}/avoid-keep-awake-react-native-fn.js (100%) rename test-project/src/{ => js}/avoid-keep-awake-react-native-hook.js (100%) rename test-project/src/{ => js}/import-all-from-library.js (100%) rename test-project/src/{ => js}/limit-db-query-results.js (100%) rename test-project/src/{ => js}/modular-import-from-library.js (100%) rename test-project/src/{ => js}/no-css-animations.js (100%) rename test-project/src/{ => js}/no-empty-image-src-attribute.js (100%) rename test-project/src/{ => js}/no-imported-number-format-library.js (100%) rename test-project/src/{ => js}/no-multiple-access-dom-element.js (100%) rename test-project/src/{ => js}/no-torch.js (100%) rename test-project/src/{ => js}/prefer-lighter-formats-for-image-files.js (100%) rename test-project/src/{ => js}/prefer-shorthand-css-notations.js (100%) rename test-project/src/{ => js}/provide-print-css.js (100%) rename test-project/src/{ => js}/rule-no-multiple-style-changes.js (100%) rename test-project/src/{ => ts}/prefer-collections-with-pagination.ts (100%) create mode 100644 test-project/src/vue/avoid-autoplay.vue diff --git a/eslint-plugin/docs/rules/avoid-autoplay.md b/eslint-plugin/docs/rules/avoid-autoplay.md index 24d9a32..3f1975d 100644 --- a/eslint-plugin/docs/rules/avoid-autoplay.md +++ b/eslint-plugin/docs/rules/avoid-autoplay.md @@ -28,7 +28,15 @@ return ( ); ``` -This rule is build for [React](https://react.dev/) and JSX. +This rule supports [React](https://react.dev/) (JSX) and Vue SFC templates when using `vue-eslint-parser`. + + + +Vue support requires `vue-eslint-parser` so the rule can access the template AST. +Dynamic bindings like `:preload="x"` are not validated by this rule. ## Resources diff --git a/eslint-plugin/lib/rules/avoid-autoplay.js b/eslint-plugin/lib/rules/avoid-autoplay.js index 350d319..c04e3b1 100644 --- a/eslint-plugin/lib/rules/avoid-autoplay.js +++ b/eslint-plugin/lib/rules/avoid-autoplay.js @@ -35,7 +35,57 @@ module.exports = { }, schema: [], }, - create(context) { + create(context) { + const reportAutoplay = (autoplayAttr, preloadAttr, preloadValue, fallback) => { + if (autoplayAttr && preloadValue !== "none") { + context.report({ + node: autoplayAttr || preloadAttr, + messageId: "NoAutoplayAndEnforcePreloadNone", + }); + return; + } + + if (autoplayAttr) { + context.report({ + node: autoplayAttr, + messageId: "NoAutoplay", + }); + } + + if (!preloadAttr || preloadValue !== "none") { + context.report({ + node: preloadAttr || fallback, + messageId: "EnforcePreloadNone", + }); + } + }; + + const parserServices = context.parserServices || context.sourceCode?.parserServices; + + const vueTemplateVisitor = parserServices?.defineTemplateBodyVisitor + ? parserServices.defineTemplateBodyVisitor({ + VElement(node) { + const rawName = + typeof node.name === "string" ? node.name : node.name?.name || node.rawName; + const name = rawName?.toLowerCase(); + if (name !== "video" && name !== "audio") return; + + const getAttr = (attrName) => + node.startTag.attributes.find( + (attr) => + attr.type === "VAttribute" && + attr.key?.name?.toLowerCase() === attrName, + ); + + const autoplayAttr = getAttr("autoplay"); + const preloadAttr = getAttr("preload"); + const preloadValue = preloadAttr?.value?.value; + + reportAutoplay(autoplayAttr, preloadAttr, preloadValue, node); + }, + }) + : {}; + return { JSXOpeningElement(node) { if (node.name.name === "video" || node.name.name === "audio") { @@ -45,31 +95,12 @@ module.exports = { const preloadAttr = node.attributes.find( (attr) => attr.name?.name.toLowerCase() === "preload", ); - if ( - autoplayAttr && - (!preloadAttr || preloadAttr.value.value !== "none") - ) { - context.report({ - node: autoplayAttr || preloadAttr, - messageId: "NoAutoplayAndEnforcePreloadNone", - }); - } else { - if (autoplayAttr) { - context.report({ - node: autoplayAttr, - messageId: "NoAutoplay", - }); - } + const preloadValue = preloadAttr?.value?.value; - if (!preloadAttr || preloadAttr.value.value !== "none") { - context.report({ - node: preloadAttr || node, - messageId: "EnforcePreloadNone", - }); - } - } + reportAutoplay(autoplayAttr, preloadAttr, preloadValue, node); } }, + ...vueTemplateVisitor, }; }, }; diff --git a/eslint-plugin/package.json b/eslint-plugin/package.json index 2b2eaf5..88be996 100644 --- a/eslint-plugin/package.json +++ b/eslint-plugin/package.json @@ -46,7 +46,8 @@ "mkdirp": "^3.0.1", "prettier": "^3.8.3", "rimraf": "^6.1.3", - "typescript": "~5.9.3" + "typescript": "~5.9.3", + "vue-eslint-parser": "^10.4.0" }, "engines": { "node": ">=22" diff --git a/eslint-plugin/tests/lib/rules/avoid-autoplay.test.js b/eslint-plugin/tests/lib/rules/avoid-autoplay.test.js index 8abb48e..2072b24 100644 --- a/eslint-plugin/tests/lib/rules/avoid-autoplay.test.js +++ b/eslint-plugin/tests/lib/rules/avoid-autoplay.test.js @@ -42,6 +42,17 @@ const ruleTester = new RuleTester({ }, }); +const vueRuleTester = new RuleTester({ + languageOptions: { + parser: require("vue-eslint-parser"), + parserOptions: { + ecmaVersion: 2021, + sourceType: "module", + parser: require("@typescript-eslint/parser"), // or "espree" + }, + }, +}); + const noAutoplayError = { messageId: "NoAutoplay", }; @@ -92,3 +103,30 @@ describe('avoid-autoplay', () => { }); }); + +const vueTests = { + valid: [ + "", + "", + ], + invalid: [ + { + code: "", + errors: [BothError], + }, + { + code: "", + errors: [BothError], + }, + { + code: "", + errors: [enforcePreloadNoneError], + }, + ], +}; + +describe("avoid-autoplay (vue)", () => { + it("autoplay-audio-video-vue-template", () => { + vueRuleTester.run("avoid-autoplay", rule, vueTests); + }); +}); \ No newline at end of file diff --git a/eslint-plugin/yarn.lock b/eslint-plugin/yarn.lock index 2164d4b..918d65c 100644 --- a/eslint-plugin/yarn.lock +++ b/eslint-plugin/yarn.lock @@ -48,6 +48,7 @@ __metadata: prettier: "npm:^3.8.3" rimraf: "npm:^6.1.3" typescript: "npm:~5.9.3" + vue-eslint-parser: "npm:^10.4.0" peerDependencies: eslint: ^9.0.0 || ^10.0.0 languageName: unknown @@ -560,7 +561,7 @@ __metadata: languageName: node linkType: hard -"debug@npm:^4.3.1, debug@npm:^4.3.2, debug@npm:^4.4.3": +"debug@npm:^4.3.1, debug@npm:^4.3.2, debug@npm:^4.4.0, debug@npm:^4.4.3": version: 4.4.3 resolution: "debug@npm:4.4.3" dependencies: @@ -731,7 +732,7 @@ __metadata: languageName: node linkType: hard -"eslint-scope@npm:^9.1.2": +"eslint-scope@npm:^8.2.0 || ^9.0.0, eslint-scope@npm:^9.1.2": version: 9.1.2 resolution: "eslint-scope@npm:9.1.2" dependencies: @@ -750,7 +751,7 @@ __metadata: languageName: node linkType: hard -"eslint-visitor-keys@npm:^5.0.0, eslint-visitor-keys@npm:^5.0.1": +"eslint-visitor-keys@npm:^4.2.0 || ^5.0.0, eslint-visitor-keys@npm:^5.0.0, eslint-visitor-keys@npm:^5.0.1": version: 5.0.1 resolution: "eslint-visitor-keys@npm:5.0.1" checksum: 10/f9cc1a57b75e0ef949545cac33d01e8367e302de4c1483266ed4d8646ee5c306376660196bbb38b004e767b7043d1e661cb4336b49eff634a1bbe75c1db709ec @@ -802,7 +803,7 @@ __metadata: languageName: node linkType: hard -"espree@npm:^11.2.0": +"espree@npm:^10.3.0 || ^11.0.0, espree@npm:^11.2.0": version: 11.2.0 resolution: "espree@npm:11.2.0" dependencies: @@ -813,7 +814,7 @@ __metadata: languageName: node linkType: hard -"esquery@npm:^1.7.0": +"esquery@npm:^1.6.0, esquery@npm:^1.7.0": version: 1.7.0 resolution: "esquery@npm:1.7.0" dependencies: @@ -1399,6 +1400,15 @@ __metadata: languageName: node linkType: hard +"semver@npm:^7.6.3": + version: 7.8.0 + resolution: "semver@npm:7.8.0" + bin: + semver: bin/semver.js + checksum: 10/039a8f68a581c03c1ac17c990316da57a79a93af9b109b712739c50cd4d464079f7e3fee31c008b472e390c7ba48a11ed2b86e91d8602bf06059d4a266db1426 + languageName: node + linkType: hard + "semver@npm:^7.7.2, semver@npm:^7.7.3": version: 7.7.3 resolution: "semver@npm:7.7.3" @@ -1575,6 +1585,22 @@ __metadata: languageName: node linkType: hard +"vue-eslint-parser@npm:^10.4.0": + version: 10.4.0 + resolution: "vue-eslint-parser@npm:10.4.0" + dependencies: + debug: "npm:^4.4.0" + eslint-scope: "npm:^8.2.0 || ^9.0.0" + eslint-visitor-keys: "npm:^4.2.0 || ^5.0.0" + espree: "npm:^10.3.0 || ^11.0.0" + esquery: "npm:^1.6.0" + semver: "npm:^7.6.3" + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + checksum: 10/b0d257ffc6afdc6fa47acd8633d3aa4959d1417fe182cd66c7fed02e0425dc9e38f6c4ee2182dbc098be14178801c3c731bd424eaad202b975ddf0756819259f + languageName: node + linkType: hard + "which@npm:^2.0.1": version: 2.0.2 resolution: "which@npm:2.0.2" diff --git a/test-project/eslint.config.mjs b/test-project/eslint.config.mjs index ba13826..1622194 100644 --- a/test-project/eslint.config.mjs +++ b/test-project/eslint.config.mjs @@ -16,4 +16,16 @@ export default defineConfig( }, }, }, + { + files: ["**/*.vue"], + languageOptions: { + parser: vueParser, + parserOptions: { + ecmaVersion: 2021, + sourceType: "module", + parser: tseslint.parser, // use "espree" if not using TS + extraFileExtensions: [".vue"], + }, + }, + }, ); diff --git a/test-project/package.json b/test-project/package.json index 48eccd6..ac03f63 100644 --- a/test-project/package.json +++ b/test-project/package.json @@ -9,8 +9,8 @@ "license": "GPL-3.0", "author": "Green Code Initiative", "scripts": { - "lint": "eslint src/.", - "lint:report": "eslint src/. -f json -o eslint-report.json", + "lint": "eslint --ext .js,.ts,.vue src", + "lint:report": "eslint --ext .js,.ts,.vue src -f json -o eslint-report.json", "sonar": "sonar" }, "devDependencies": { diff --git a/test-project/src/avoid-autoplay.js b/test-project/src/js/avoid-autoplay.js similarity index 100% rename from test-project/src/avoid-autoplay.js rename to test-project/src/js/avoid-autoplay.js diff --git a/test-project/src/avoid-brightness-override.js b/test-project/src/js/avoid-brightness-override.js similarity index 100% rename from test-project/src/avoid-brightness-override.js rename to test-project/src/js/avoid-brightness-override.js diff --git a/test-project/src/avoid-high-accuracy-geolocation.js b/test-project/src/js/avoid-high-accuracy-geolocation.js similarity index 100% rename from test-project/src/avoid-high-accuracy-geolocation.js rename to test-project/src/js/avoid-high-accuracy-geolocation.js diff --git a/test-project/src/avoid-keep-awake-react-native-fn.js b/test-project/src/js/avoid-keep-awake-react-native-fn.js similarity index 100% rename from test-project/src/avoid-keep-awake-react-native-fn.js rename to test-project/src/js/avoid-keep-awake-react-native-fn.js diff --git a/test-project/src/avoid-keep-awake-react-native-hook.js b/test-project/src/js/avoid-keep-awake-react-native-hook.js similarity index 100% rename from test-project/src/avoid-keep-awake-react-native-hook.js rename to test-project/src/js/avoid-keep-awake-react-native-hook.js diff --git a/test-project/src/import-all-from-library.js b/test-project/src/js/import-all-from-library.js similarity index 100% rename from test-project/src/import-all-from-library.js rename to test-project/src/js/import-all-from-library.js diff --git a/test-project/src/limit-db-query-results.js b/test-project/src/js/limit-db-query-results.js similarity index 100% rename from test-project/src/limit-db-query-results.js rename to test-project/src/js/limit-db-query-results.js diff --git a/test-project/src/modular-import-from-library.js b/test-project/src/js/modular-import-from-library.js similarity index 100% rename from test-project/src/modular-import-from-library.js rename to test-project/src/js/modular-import-from-library.js diff --git a/test-project/src/no-css-animations.js b/test-project/src/js/no-css-animations.js similarity index 100% rename from test-project/src/no-css-animations.js rename to test-project/src/js/no-css-animations.js diff --git a/test-project/src/no-empty-image-src-attribute.js b/test-project/src/js/no-empty-image-src-attribute.js similarity index 100% rename from test-project/src/no-empty-image-src-attribute.js rename to test-project/src/js/no-empty-image-src-attribute.js diff --git a/test-project/src/no-imported-number-format-library.js b/test-project/src/js/no-imported-number-format-library.js similarity index 100% rename from test-project/src/no-imported-number-format-library.js rename to test-project/src/js/no-imported-number-format-library.js diff --git a/test-project/src/no-multiple-access-dom-element.js b/test-project/src/js/no-multiple-access-dom-element.js similarity index 100% rename from test-project/src/no-multiple-access-dom-element.js rename to test-project/src/js/no-multiple-access-dom-element.js diff --git a/test-project/src/no-torch.js b/test-project/src/js/no-torch.js similarity index 100% rename from test-project/src/no-torch.js rename to test-project/src/js/no-torch.js diff --git a/test-project/src/prefer-lighter-formats-for-image-files.js b/test-project/src/js/prefer-lighter-formats-for-image-files.js similarity index 100% rename from test-project/src/prefer-lighter-formats-for-image-files.js rename to test-project/src/js/prefer-lighter-formats-for-image-files.js diff --git a/test-project/src/prefer-shorthand-css-notations.js b/test-project/src/js/prefer-shorthand-css-notations.js similarity index 100% rename from test-project/src/prefer-shorthand-css-notations.js rename to test-project/src/js/prefer-shorthand-css-notations.js diff --git a/test-project/src/provide-print-css.js b/test-project/src/js/provide-print-css.js similarity index 100% rename from test-project/src/provide-print-css.js rename to test-project/src/js/provide-print-css.js diff --git a/test-project/src/rule-no-multiple-style-changes.js b/test-project/src/js/rule-no-multiple-style-changes.js similarity index 100% rename from test-project/src/rule-no-multiple-style-changes.js rename to test-project/src/js/rule-no-multiple-style-changes.js diff --git a/test-project/src/prefer-collections-with-pagination.ts b/test-project/src/ts/prefer-collections-with-pagination.ts similarity index 100% rename from test-project/src/prefer-collections-with-pagination.ts rename to test-project/src/ts/prefer-collections-with-pagination.ts diff --git a/test-project/src/vue/avoid-autoplay.vue b/test-project/src/vue/avoid-autoplay.vue new file mode 100644 index 0000000..6b2c9df --- /dev/null +++ b/test-project/src/vue/avoid-autoplay.vue @@ -0,0 +1,4 @@ + \ No newline at end of file From a3db2a138b23c1747037c3733b3019c8d6e593c0 Mon Sep 17 00:00:00 2001 From: Neptia Date: Tue, 19 May 2026 16:04:27 +0200 Subject: [PATCH 2/4] Add Vue support to ESLint rules and update documentation for better integration --- .../docs/rules/avoid-css-animations.md | 7 +++ .../rules/no-empty-image-src-attribute.md | 11 ++++- .../prefer-lighter-formats-for-image-files.md | 7 +++ .../rules/prefer-shorthand-css-notations.md | 7 +++ eslint-plugin/lib/rules/avoid-autoplay.js | 9 ++-- .../lib/rules/avoid-css-animations.js | 31 +++++++++++- .../lib/rules/no-empty-image-src-attribute.js | 31 +++++++++++- .../prefer-lighter-formats-for-image-files.js | 41 ++++++++++++++-- .../rules/prefer-shorthand-css-notations.js | 49 ++++++++++++++++++- .../tests/lib/rules/avoid-autoplay.test.js | 2 +- .../lib/rules/avoid-css-animations.test.js | 34 +++++++++++++ .../no-empty-image-src-attribute.test.js | 34 +++++++++++++ ...er-lighter-formats-for-image-files.test.js | 37 ++++++++++++++ .../prefer-shorthand-css-notations.test.js | 39 +++++++++++++++ test-project/eslint.config.mjs | 1 + test-project/package.json | 3 +- test-project/src/vue/avoid-css-animations.vue | 4 ++ .../src/vue/no-empty-image-src-attribute.vue | 5 ++ ...prefer-lighter-formats-for-image-files.vue | 4 ++ .../vue/prefer-shorthand-css-notations.vue | 4 ++ test-project/yarn.lock | 40 ++++++++++++--- 21 files changed, 379 insertions(+), 21 deletions(-) create mode 100644 test-project/src/vue/avoid-css-animations.vue create mode 100644 test-project/src/vue/no-empty-image-src-attribute.vue create mode 100644 test-project/src/vue/prefer-lighter-formats-for-image-files.vue create mode 100644 test-project/src/vue/prefer-shorthand-css-notations.vue diff --git a/eslint-plugin/docs/rules/avoid-css-animations.md b/eslint-plugin/docs/rules/avoid-css-animations.md index 77ff251..bdfe4eb 100644 --- a/eslint-plugin/docs/rules/avoid-css-animations.md +++ b/eslint-plugin/docs/rules/avoid-css-animations.md @@ -24,6 +24,13 @@ Limiting the usage of CSS animations helps in creating a more energy-efficient a
// Compliant ``` + + +Vue support only checks static style attributes; :style bindings are not validated. + It's important to note that while limiting animations is generally advisable for certain scenarios, there are cases where animations contribute positively to the user experience and overall design. In this case they should be limited to the CSS properties `opacity` and `transform` with it's associated diff --git a/eslint-plugin/docs/rules/no-empty-image-src-attribute.md b/eslint-plugin/docs/rules/no-empty-image-src-attribute.md index 4d8c10f..b1f64ec 100644 --- a/eslint-plugin/docs/rules/no-empty-image-src-attribute.md +++ b/eslint-plugin/docs/rules/no-empty-image-src-attribute.md @@ -40,7 +40,16 @@ return ( ); ``` -This rule is build for [React](https://react.dev/) and JSX. +This rule supports React (JSX) and Vue SFC templates when using vue-eslint-parser. + + + +Vue support requires vue-eslint-parser; dynamic bindings like :src are not validated by this rule. + ## Resources diff --git a/eslint-plugin/docs/rules/prefer-lighter-formats-for-image-files.md b/eslint-plugin/docs/rules/prefer-lighter-formats-for-image-files.md index 9c9aa4c..f742e47 100644 --- a/eslint-plugin/docs/rules/prefer-lighter-formats-for-image-files.md +++ b/eslint-plugin/docs/rules/prefer-lighter-formats-for-image-files.md @@ -72,6 +72,13 @@ is supported, the image.webp image will be downloaded; otherwise, image.jpg imag ``` + + +Vue support requires vue-eslint-parser; dynamic bindings like :src are not validated by this rule. + Also remember to consider browser compatibility. Older browsers may not recognize .webp/.avif images and fail to display them. To address this issue, you can supply multiple formats for the same image. diff --git a/eslint-plugin/docs/rules/prefer-shorthand-css-notations.md b/eslint-plugin/docs/rules/prefer-shorthand-css-notations.md index 707c896..d5b7233 100644 --- a/eslint-plugin/docs/rules/prefer-shorthand-css-notations.md +++ b/eslint-plugin/docs/rules/prefer-shorthand-css-notations.md @@ -64,6 +64,13 @@ For example, if you only want to set the left margin, you must continue to use `
``` + + +Vue support only checks static style attributes; :style bindings are not validated. + This optimization works for a number of properties [listed here](https://developer.mozilla.org/en-US/docs/Web/CSS/Shorthand_properties#see_also). diff --git a/eslint-plugin/lib/rules/avoid-autoplay.js b/eslint-plugin/lib/rules/avoid-autoplay.js index c04e3b1..765adc1 100644 --- a/eslint-plugin/lib/rules/avoid-autoplay.js +++ b/eslint-plugin/lib/rules/avoid-autoplay.js @@ -35,7 +35,7 @@ module.exports = { }, schema: [], }, - create(context) { + create(context) { const reportAutoplay = (autoplayAttr, preloadAttr, preloadValue, fallback) => { if (autoplayAttr && preloadValue !== "none") { context.report({ @@ -60,13 +60,16 @@ module.exports = { } }; - const parserServices = context.parserServices || context.sourceCode?.parserServices; + const parserServices = + context.parserServices || context.sourceCode?.parserServices; const vueTemplateVisitor = parserServices?.defineTemplateBodyVisitor ? parserServices.defineTemplateBodyVisitor({ VElement(node) { const rawName = - typeof node.name === "string" ? node.name : node.name?.name || node.rawName; + typeof node.name === "string" + ? node.name + : node.name?.name || node.rawName; const name = rawName?.toLowerCase(); if (name !== "video" && name !== "audio") return; diff --git a/eslint-plugin/lib/rules/avoid-css-animations.js b/eslint-plugin/lib/rules/avoid-css-animations.js index f181671..ae3350a 100644 --- a/eslint-plugin/lib/rules/avoid-css-animations.js +++ b/eslint-plugin/lib/rules/avoid-css-animations.js @@ -32,8 +32,35 @@ module.exports = { }, schema: [], }, - create(context) { + create(context) { const forbiddenProperties = ["transition", "animation"]; + + const parserServices = + context.parserServices || context.sourceCode?.parserServices; + + const vueTemplateVisitor = parserServices?.defineTemplateBodyVisitor + ? parserServices.defineTemplateBodyVisitor({ + VElement(node) { + const styleAttr = node.startTag.attributes.find( + (attr) => attr.type === "VAttribute" && attr.key?.name === "style", + ); + const styleValue = styleAttr?.value?.value; + if (!styleValue) return; + + const matched = forbiddenProperties.find((prop) => + new RegExp(`(^|;)\\s*${prop}\\s*:`, "i").test(styleValue), + ); + if (!matched) return; + + context.report({ + node: styleAttr, + messageId: "AvoidCSSAnimations", + data: { attribute: matched }, + }); + }, + }) + : {}; + return { JSXOpeningElement(node) { const styleAttribute = node.attributes.find( @@ -41,7 +68,6 @@ module.exports = { ); if (styleAttribute?.value.expression?.properties) { - // To prevent (for example)
const property = styleAttribute.value.expression.properties.find( (prop) => prop.key != null && @@ -60,6 +86,7 @@ module.exports = { } } }, + ...vueTemplateVisitor, }; }, }; diff --git a/eslint-plugin/lib/rules/no-empty-image-src-attribute.js b/eslint-plugin/lib/rules/no-empty-image-src-attribute.js index ed0d148..22480c6 100644 --- a/eslint-plugin/lib/rules/no-empty-image-src-attribute.js +++ b/eslint-plugin/lib/rules/no-empty-image-src-attribute.js @@ -34,6 +34,34 @@ module.exports = { schema: [], }, create(context) { + const parserServices = + context.parserServices || context.sourceCode?.parserServices; + + const vueTemplateVisitor = parserServices?.defineTemplateBodyVisitor + ? parserServices.defineTemplateBodyVisitor({ + VElement(node) { + const rawName = + typeof node.name === "string" + ? node.name + : node.name?.name || node.rawName; + const name = rawName?.toLowerCase(); + if (name !== "img") return; + + const srcAttr = node.startTag.attributes.find( + (attr) => attr.type === "VAttribute" && attr.key?.name === "src", + ); + const srcValue = srcAttr?.value?.value; + + if (srcValue === "" || !srcAttr) { + context.report({ + node: srcAttr || node, + messageId: "SpecifySrcAttribute", + }); + } + }, + }) + : {}; + return { JSXOpeningElement(node) { if (node.name.name === "img") { @@ -41,13 +69,11 @@ module.exports = { (attr) => attr.name.name === "src", ); if (srcValue?.value?.value === "") { - //to prevent Empty image context.report({ node: srcValue, messageId: "SpecifySrcAttribute", }); } else if (!srcValue) { - //to prevent context.report({ node, messageId: "SpecifySrcAttribute", @@ -55,6 +81,7 @@ module.exports = { } } }, + ...vueTemplateVisitor, }; }, }; diff --git a/eslint-plugin/lib/rules/prefer-lighter-formats-for-image-files.js b/eslint-plugin/lib/rules/prefer-lighter-formats-for-image-files.js index f3c9c85..d73f697 100644 --- a/eslint-plugin/lib/rules/prefer-lighter-formats-for-image-files.js +++ b/eslint-plugin/lib/rules/prefer-lighter-formats-for-image-files.js @@ -36,7 +36,42 @@ module.exports = { create(context) { const eligibleExtensions = ["webp", "avif", "svg", "jxl"]; - return { + const parserServices = context.parserServices || context.sourceCode?.parserServices; + + const vueTemplateVisitor = parserServices?.defineTemplateBodyVisitor + ? parserServices.defineTemplateBodyVisitor({ + VElement(node) { + const name = + typeof node.name === "string" ? node.name : node.name?.name; + if (name?.toLowerCase() !== "img") return; + + const parent = node.parent?.type === "VElement" ? node.parent : null; + const parentName = + typeof parent?.name === "string" ? parent.name : parent?.name?.name; + if (parentName?.toLowerCase() === "picture") return; + + const srcAttr = node.startTag.attributes.find( + (attr) => attr.type === "VAttribute" && attr.key?.name === "src", + ); + const srcValue = srcAttr?.value?.value; + if (!srcValue) return; + + const fileName = srcValue.substring(srcValue.lastIndexOf("/") + 1); + const dotIndex = fileName.lastIndexOf("."); + if (dotIndex === -1) return; + + const imgExtension = fileName.substring(dotIndex + 1); + if (eligibleExtensions.includes(imgExtension.toLowerCase())) return; + + context.report({ + node, + messageId: "PreferLighterFormatsForImageFiles", + data: { eligibleExtensions: eligibleExtensions.join(", ") }, + }); + }, + }) + : {}; + return { JSXOpeningElement(node) { const tagName = node.name.name; if (tagName?.toLowerCase() !== "img") return; @@ -66,7 +101,7 @@ module.exports = { messageId: "PreferLighterFormatsForImageFiles", data: { eligibleExtensions: eligibleExtensions.join(", ") }, }); - }, - }; + } + , ...vueTemplateVisitor }; }, }; diff --git a/eslint-plugin/lib/rules/prefer-shorthand-css-notations.js b/eslint-plugin/lib/rules/prefer-shorthand-css-notations.js index 92d4b8c..400af0d 100644 --- a/eslint-plugin/lib/rules/prefer-shorthand-css-notations.js +++ b/eslint-plugin/lib/rules/prefer-shorthand-css-notations.js @@ -52,7 +52,7 @@ module.exports = { }, ], }, - create: function (context) { + create: function (context) { const shorthandProperties = { animation: ["animationName", "animationDuration"], background: [ @@ -87,6 +87,52 @@ module.exports = { const disabledProperties = context.options?.[0]?.disableProperties ?? []; + const parserServices = + context.parserServices || context.sourceCode?.parserServices; + + const toCamelCase = (value) => + value.replace(/-([a-z])/g, (_, letter) => letter.toUpperCase()); + + const parseCssProperties = (styleValue) => + styleValue + .split(";") + .map((part) => part.trim()) + .filter(Boolean) + .map((part) => part.split(":")[0].trim()) + .filter(Boolean) + .map(toCamelCase); + + const vueTemplateVisitor = parserServices?.defineTemplateBodyVisitor + ? parserServices.defineTemplateBodyVisitor({ + VElement(node) { + const styleAttr = node.startTag.attributes.find( + (attr) => attr.type === "VAttribute" && attr.key?.name === "style", + ); + const styleValue = styleAttr?.value?.value; + if (!styleValue) return; + + const nodePropertyNames = parseCssProperties(styleValue); + + for (const [shorthandProp, matchProperties] of Object.entries( + shorthandProperties, + )) { + if ( + !disabledProperties.includes(shorthandProp) && + matchProperties.every((prop) => + nodePropertyNames.includes(prop), + ) + ) { + return context.report({ + node: styleAttr, + messageId: "PreferShorthandCSSNotation", + data: { property: shorthandProp }, + }); + } + } + }, + }) + : {}; + return { JSXOpeningElement(node) { const styleAttribute = node.attributes.find( @@ -113,6 +159,7 @@ module.exports = { } } }, + ...vueTemplateVisitor, }; }, }; diff --git a/eslint-plugin/tests/lib/rules/avoid-autoplay.test.js b/eslint-plugin/tests/lib/rules/avoid-autoplay.test.js index 2072b24..4482f38 100644 --- a/eslint-plugin/tests/lib/rules/avoid-autoplay.test.js +++ b/eslint-plugin/tests/lib/rules/avoid-autoplay.test.js @@ -48,7 +48,7 @@ const vueRuleTester = new RuleTester({ parserOptions: { ecmaVersion: 2021, sourceType: "module", - parser: require("@typescript-eslint/parser"), // or "espree" + parser: require("@typescript-eslint/parser"), }, }, }); diff --git a/eslint-plugin/tests/lib/rules/avoid-css-animations.test.js b/eslint-plugin/tests/lib/rules/avoid-css-animations.test.js index ea598d4..7ef74b8 100644 --- a/eslint-plugin/tests/lib/rules/avoid-css-animations.test.js +++ b/eslint-plugin/tests/lib/rules/avoid-css-animations.test.js @@ -42,6 +42,17 @@ const ruleTester = new RuleTester({ }, }); +const vueRuleTester = new RuleTester({ + languageOptions: { + parser: require("vue-eslint-parser"), + parserOptions: { + ecmaVersion: 2021, + sourceType: "module", + parser: require("@typescript-eslint/parser"), + }, + }, +}); + const tests = { valid: [ ` @@ -93,3 +104,26 @@ describe("avoid-css-animations", () => { ruleTester.run("avoid-css-animations", rule, tests); }); }); + +const vueTests = { + valid: [ + "", + "", + ], + invalid: [ + { + code: "", + errors: [{ messageId: "AvoidCSSAnimations", data: { attribute: "transition" } }], + }, + { + code: "", + errors: [{ messageId: "AvoidCSSAnimations", data: { attribute: "animation" } }], + }, + ], +}; + +describe("avoid-css-animations (vue)", () => { + it("avoid-css-animations-vue", () => { + vueRuleTester.run("avoid-css-animations", rule, vueTests); + }); +}); \ No newline at end of file diff --git a/eslint-plugin/tests/lib/rules/no-empty-image-src-attribute.test.js b/eslint-plugin/tests/lib/rules/no-empty-image-src-attribute.test.js index 8f8912d..478f3e4 100644 --- a/eslint-plugin/tests/lib/rules/no-empty-image-src-attribute.test.js +++ b/eslint-plugin/tests/lib/rules/no-empty-image-src-attribute.test.js @@ -41,6 +41,18 @@ const ruleTester = new RuleTester({ }, }, }); + +const vueRuleTester = new RuleTester({ + languageOptions: { + parser: require("vue-eslint-parser"), + parserOptions: { + ecmaVersion: 2021, + sourceType: "module", + parser: require("@typescript-eslint/parser"), + }, + }, +}); + const expectedError1 = { messageId: "SpecifySrcAttribute", }; @@ -80,3 +92,25 @@ describe("no-empty-image-src-attribute", () => { ruleTester.run("image-src-attribute-not-empty", rule, tests); }); }); + +const vueTests = { + valid: [ + "", + ], + invalid: [ + { + code: "", + errors: [expectedError1], + }, + { + code: "", + errors: [expectedError2], + }, + ], +}; + +describe("no-empty-image-src-attribute (vue)", () => { + it("image-src-attribute-not-empty-vue", () => { + vueRuleTester.run("no-empty-image-src-attribute", rule, vueTests); + }); +}); \ No newline at end of file diff --git a/eslint-plugin/tests/lib/rules/prefer-lighter-formats-for-image-files.test.js b/eslint-plugin/tests/lib/rules/prefer-lighter-formats-for-image-files.test.js index 2b32475..2dd9657 100644 --- a/eslint-plugin/tests/lib/rules/prefer-lighter-formats-for-image-files.test.js +++ b/eslint-plugin/tests/lib/rules/prefer-lighter-formats-for-image-files.test.js @@ -42,6 +42,17 @@ const ruleTester = new RuleTester({ }, }); +const vueRuleTester = new RuleTester({ + languageOptions: { + parser: require("vue-eslint-parser"), + parserOptions: { + ecmaVersion: 2021, + sourceType: "module", + parser: require("@typescript-eslint/parser"), + }, + }, +}); + const preferLighterFormatsForImageFilesError = { messageId: "PreferLighterFormatsForImageFiles", }; @@ -92,3 +103,29 @@ describe("prefer-lighter-formats-for-image-files", () => { ruleTester.run("prefer-lighter-formats-for-image-files", rule, tests); }); }); + +const vueTests = { + valid: [ + "", + "", + "", + "", + "", + ], + invalid: [ + { + code: "", + errors: [preferLighterFormatsForImageFilesError], + }, + { + code: "", + errors: [preferLighterFormatsForImageFilesError], + }, + ], +}; + +describe("prefer-lighter-formats-for-image-files (vue)", () => { + it("prefer-lighter-formats-for-image-files-vue", () => { + vueRuleTester.run("prefer-lighter-formats-for-image-files", rule, vueTests); + }); +}); \ No newline at end of file diff --git a/eslint-plugin/tests/lib/rules/prefer-shorthand-css-notations.test.js b/eslint-plugin/tests/lib/rules/prefer-shorthand-css-notations.test.js index b432a22..51cb52c 100644 --- a/eslint-plugin/tests/lib/rules/prefer-shorthand-css-notations.test.js +++ b/eslint-plugin/tests/lib/rules/prefer-shorthand-css-notations.test.js @@ -42,11 +42,27 @@ const ruleTester = new RuleTester({ }, }); +const vueRuleTester = new RuleTester({ + languageOptions: { + parser: require("vue-eslint-parser"), + parserOptions: { + ecmaVersion: 2021, + sourceType: "module", + parser: require("@typescript-eslint/parser"), + }, + }, +}); + const createError = (property) => ({ messageId: "PreferShorthandCSSNotation", data: { property }, }); +const vueCreateError = (property) => ({ + messageId: "PreferShorthandCSSNotation", + data: { property }, +}); + const tests = { valid: [ "
", @@ -169,3 +185,26 @@ describe("prefer-shorthand-css-notations", () => { ruleTester.run("prefer-shorthand-css-notations", rule, tests); }); }); + +const vueTests = { + valid: [ + "", + "", + ], + invalid: [ + { + code: "", + errors: [vueCreateError("margin")], + }, + { + code: "", + errors: [vueCreateError("border")], + }, + ], +}; + +describe("prefer-shorthand-css-notations (vue)", () => { + it("prefer-shorthand-css-notations-vue", () => { + vueRuleTester.run("prefer-shorthand-css-notations", rule, vueTests); + }); +}); diff --git a/test-project/eslint.config.mjs b/test-project/eslint.config.mjs index 1622194..7e59b22 100644 --- a/test-project/eslint.config.mjs +++ b/test-project/eslint.config.mjs @@ -3,6 +3,7 @@ import eslint from "@eslint/js"; import { defineConfig } from "eslint/config"; import globals from "globals"; import tseslint from "typescript-eslint"; +import vueParser from "vue-eslint-parser"; export default defineConfig( eslint.configs.recommended, diff --git a/test-project/package.json b/test-project/package.json index ac03f63..f49e1f6 100644 --- a/test-project/package.json +++ b/test-project/package.json @@ -21,7 +21,8 @@ "eslint": "^10.2.1", "globals": "^17.5.0", "typescript": "~5.9.3", - "typescript-eslint": "^8.59.1" + "typescript-eslint": "^8.59.1", + "vue-eslint-parser": "^10.4.0" }, "packageManager": "yarn@4.14.1" } diff --git a/test-project/src/vue/avoid-css-animations.vue b/test-project/src/vue/avoid-css-animations.vue new file mode 100644 index 0000000..9db1000 --- /dev/null +++ b/test-project/src/vue/avoid-css-animations.vue @@ -0,0 +1,4 @@ + \ No newline at end of file diff --git a/test-project/src/vue/no-empty-image-src-attribute.vue b/test-project/src/vue/no-empty-image-src-attribute.vue new file mode 100644 index 0000000..ac4a89d --- /dev/null +++ b/test-project/src/vue/no-empty-image-src-attribute.vue @@ -0,0 +1,5 @@ + \ No newline at end of file diff --git a/test-project/src/vue/prefer-lighter-formats-for-image-files.vue b/test-project/src/vue/prefer-lighter-formats-for-image-files.vue new file mode 100644 index 0000000..920d4af --- /dev/null +++ b/test-project/src/vue/prefer-lighter-formats-for-image-files.vue @@ -0,0 +1,4 @@ + \ No newline at end of file diff --git a/test-project/src/vue/prefer-shorthand-css-notations.vue b/test-project/src/vue/prefer-shorthand-css-notations.vue new file mode 100644 index 0000000..e384e7a --- /dev/null +++ b/test-project/src/vue/prefer-shorthand-css-notations.vue @@ -0,0 +1,4 @@ + \ No newline at end of file diff --git a/test-project/yarn.lock b/test-project/yarn.lock index 4cd76f6..36e6b81 100644 --- a/test-project/yarn.lock +++ b/test-project/yarn.lock @@ -21,10 +21,10 @@ __metadata: "@creedengo/eslint-plugin@file:../eslint-plugin::locator=creedengo-javascript-test-project%40workspace%3A.": version: 3.1.0 - resolution: "@creedengo/eslint-plugin@file:../eslint-plugin#../eslint-plugin::hash=30e64e&locator=creedengo-javascript-test-project%40workspace%3A." + resolution: "@creedengo/eslint-plugin@file:../eslint-plugin#../eslint-plugin::hash=0d5698&locator=creedengo-javascript-test-project%40workspace%3A." peerDependencies: eslint: ^9.0.0 || ^10.0.0 - checksum: 10c0/35357906578cb26b758f44fb8fdb12656de3a6d3297d97002f3a1c755066b81e1321badd1f4a01a9e9d156b0545b5611774c2ed83e80600168c134f0dc0846fd + checksum: 10c0/84964221d68c9fb9749b37a91c05605d18c768c1ac7c693fae7a090615a2bff438507a9b91ad5ddfadc1c5d57f8c7422a6759595c6398d895d459116246a53fb languageName: node linkType: hard @@ -572,6 +572,7 @@ __metadata: globals: "npm:^17.5.0" typescript: "npm:~5.9.3" typescript-eslint: "npm:^8.59.1" + vue-eslint-parser: "npm:^10.4.0" languageName: unknown linkType: soft @@ -586,7 +587,7 @@ __metadata: languageName: node linkType: hard -"debug@npm:^4.3.1, debug@npm:^4.3.2, debug@npm:^4.4.3": +"debug@npm:^4.3.1, debug@npm:^4.3.2, debug@npm:^4.4.0, debug@npm:^4.4.3": version: 4.4.3 resolution: "debug@npm:4.4.3" dependencies: @@ -665,7 +666,7 @@ __metadata: languageName: node linkType: hard -"eslint-scope@npm:^9.1.2": +"eslint-scope@npm:^8.2.0 || ^9.0.0, eslint-scope@npm:^9.1.2": version: 9.1.2 resolution: "eslint-scope@npm:9.1.2" dependencies: @@ -684,7 +685,7 @@ __metadata: languageName: node linkType: hard -"eslint-visitor-keys@npm:^5.0.0, eslint-visitor-keys@npm:^5.0.1": +"eslint-visitor-keys@npm:^4.2.0 || ^5.0.0, eslint-visitor-keys@npm:^5.0.0, eslint-visitor-keys@npm:^5.0.1": version: 5.0.1 resolution: "eslint-visitor-keys@npm:5.0.1" checksum: 10c0/16190bdf2cbae40a1109384c94450c526a79b0b9c3cb21e544256ed85ac48a4b84db66b74a6561d20fe6ab77447f150d711c2ad5ad74df4fcc133736bce99678 @@ -736,7 +737,7 @@ __metadata: languageName: node linkType: hard -"espree@npm:^11.2.0": +"espree@npm:^10.3.0 || ^11.0.0, espree@npm:^11.2.0": version: 11.2.0 resolution: "espree@npm:11.2.0" dependencies: @@ -747,7 +748,7 @@ __metadata: languageName: node linkType: hard -"esquery@npm:^1.7.0": +"esquery@npm:^1.6.0, esquery@npm:^1.7.0": version: 1.7.0 resolution: "esquery@npm:1.7.0" dependencies: @@ -1256,6 +1257,15 @@ __metadata: languageName: node linkType: hard +"semver@npm:^7.6.3": + version: 7.8.0 + resolution: "semver@npm:7.8.0" + bin: + semver: bin/semver.js + checksum: 10c0/8f096ca9b80ffd47b308d03f9ce8c873e27e2983f36023c559cdc92c51e8433fc23ebbfe57ec9623fc155636a6961ee989501099841ae4bb1babc8d2b3f048cd + languageName: node + linkType: hard + "shebang-command@npm:^2.0.0": version: 2.0.0 resolution: "shebang-command@npm:2.0.0" @@ -1435,6 +1445,22 @@ __metadata: languageName: node linkType: hard +"vue-eslint-parser@npm:^10.4.0": + version: 10.4.0 + resolution: "vue-eslint-parser@npm:10.4.0" + dependencies: + debug: "npm:^4.4.0" + eslint-scope: "npm:^8.2.0 || ^9.0.0" + eslint-visitor-keys: "npm:^4.2.0 || ^5.0.0" + espree: "npm:^10.3.0 || ^11.0.0" + esquery: "npm:^1.6.0" + semver: "npm:^7.6.3" + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + checksum: 10c0/ded1c52dfa6e08c384da43b8369814bd105e2dc3bbb04e95faa8365af0fcba70293ff8b3749824ae3cc98988aeaaa261d5a205febff23e15667176392dcfee4a + languageName: node + linkType: hard + "which@npm:^2.0.1": version: 2.0.2 resolution: "which@npm:2.0.2" From 8758b81a4192e6c3893fd1a740a68ed0a00011d8 Mon Sep 17 00:00:00 2001 From: Mikael Picard Date: Tue, 19 May 2026 15:15:18 +0200 Subject: [PATCH 3/4] fix: Use correct path for PGDATA variable in docker-compose --- docker-compose.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker-compose.yml b/docker-compose.yml index c10591d..fe75a88 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -41,7 +41,7 @@ services: POSTGRES_USER: sonar POSTGRES_PASSWORD: sonar POSTGRES_DB: sonarqube - PGDATA: pg_data:/var/lib/postgresql/data/pgdata + PGDATA: /var/lib/postgresql/data/pgdata healthcheck: test: ["CMD-SHELL", "pg_isready -U sonar -d sonarqube"] interval: 5s From 1a331069e06a63344676e78f7d85ed549fc53e06 Mon Sep 17 00:00:00 2001 From: Neptia Date: Wed, 20 May 2026 14:50:50 +0200 Subject: [PATCH 4/4] Enhance Vue support in ESLint rules and documentation, including new template examples and compliance notes --- CHANGELOG.md | 2 ++ eslint-plugin/docs/rules/avoid-autoplay.md | 7 +++---- eslint-plugin/docs/rules/avoid-css-animations.md | 2 ++ eslint-plugin/docs/rules/no-empty-image-src-attribute.md | 7 +++---- .../docs/rules/prefer-lighter-formats-for-image-files.md | 4 ++-- eslint-plugin/docs/rules/prefer-shorthand-css-notations.md | 2 ++ eslint-plugin/lib/rules/avoid-css-animations.js | 1 + eslint-plugin/lib/rules/no-empty-image-src-attribute.js | 2 ++ test-project/package.json | 4 ++-- test-project/src/vue/avoid-autoplay.vue | 5 +++-- test-project/src/vue/avoid-css-animations.vue | 2 +- test-project/src/vue/no-empty-image-src-attribute.vue | 4 ++-- .../src/vue/prefer-lighter-formats-for-image-files.vue | 2 +- test-project/src/vue/prefer-shorthand-css-notations.vue | 2 +- 14 files changed, 27 insertions(+), 19 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5d22fe0..f0f792a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed - [#107](https://github.com/green-code-initiative/creedengo-javascript/pull/107) Move to creedengo-rules-specifications v3 +- [#115](https://github.com/green-code-initiative/creedengo-javascript/pull/115) Add Vue SFC template support to JSX‑based rules (avoid-autoplay, no-empty-image-src-attribute, prefer-lighter-formats-for-image-files, avoid-css-animations, prefer-shorthand-css-notations) +- [#115](https://github.com/green-code-initiative/creedengo-javascript/pull/115) Add Vue RuleTester cases and test-project Vue examples for the updated rules ### Fixed diff --git a/eslint-plugin/docs/rules/avoid-autoplay.md b/eslint-plugin/docs/rules/avoid-autoplay.md index 3f1975d..e7f39c1 100644 --- a/eslint-plugin/docs/rules/avoid-autoplay.md +++ b/eslint-plugin/docs/rules/avoid-autoplay.md @@ -28,15 +28,14 @@ return ( ); ``` -This rule supports [React](https://react.dev/) (JSX) and Vue SFC templates when using `vue-eslint-parser`. +This rule is build for [React](https://react.dev/) and JSX. +```jsx - -Vue support requires `vue-eslint-parser` so the rule can access the template AST. -Dynamic bindings like `:preload="x"` are not validated by this rule. +``` ## Resources diff --git a/eslint-plugin/docs/rules/avoid-css-animations.md b/eslint-plugin/docs/rules/avoid-css-animations.md index bdfe4eb..d5bab67 100644 --- a/eslint-plugin/docs/rules/avoid-css-animations.md +++ b/eslint-plugin/docs/rules/avoid-css-animations.md @@ -24,10 +24,12 @@ Limiting the usage of CSS animations helps in creating a more energy-efficient a
// Compliant ``` +```jsx +``` Vue support only checks static style attributes; :style bindings are not validated. diff --git a/eslint-plugin/docs/rules/no-empty-image-src-attribute.md b/eslint-plugin/docs/rules/no-empty-image-src-attribute.md index b1f64ec..83bb8e7 100644 --- a/eslint-plugin/docs/rules/no-empty-image-src-attribute.md +++ b/eslint-plugin/docs/rules/no-empty-image-src-attribute.md @@ -40,16 +40,15 @@ return ( ); ``` -This rule supports React (JSX) and Vue SFC templates when using vue-eslint-parser. +This rule is build for [React](https://react.dev/) and JSX. +```jsx - -Vue support requires vue-eslint-parser; dynamic bindings like :src are not validated by this rule. - +``` ## Resources diff --git a/eslint-plugin/docs/rules/prefer-lighter-formats-for-image-files.md b/eslint-plugin/docs/rules/prefer-lighter-formats-for-image-files.md index f742e47..3b73ad7 100644 --- a/eslint-plugin/docs/rules/prefer-lighter-formats-for-image-files.md +++ b/eslint-plugin/docs/rules/prefer-lighter-formats-for-image-files.md @@ -72,12 +72,12 @@ is supported, the image.webp image will be downloaded; otherwise, image.jpg imag ``` +```jsx - -Vue support requires vue-eslint-parser; dynamic bindings like :src are not validated by this rule. +``` Also remember to consider browser compatibility. Older browsers may not recognize .webp/.avif images and fail to display them. diff --git a/eslint-plugin/docs/rules/prefer-shorthand-css-notations.md b/eslint-plugin/docs/rules/prefer-shorthand-css-notations.md index d5b7233..0f9fa51 100644 --- a/eslint-plugin/docs/rules/prefer-shorthand-css-notations.md +++ b/eslint-plugin/docs/rules/prefer-shorthand-css-notations.md @@ -64,10 +64,12 @@ For example, if you only want to set the left margin, you must continue to use `
``` +```jsx +``` Vue support only checks static style attributes; :style bindings are not validated. diff --git a/eslint-plugin/lib/rules/avoid-css-animations.js b/eslint-plugin/lib/rules/avoid-css-animations.js index ae3350a..0de0900 100644 --- a/eslint-plugin/lib/rules/avoid-css-animations.js +++ b/eslint-plugin/lib/rules/avoid-css-animations.js @@ -68,6 +68,7 @@ module.exports = { ); if (styleAttribute?.value.expression?.properties) { + // To prevent (for example)
const property = styleAttribute.value.expression.properties.find( (prop) => prop.key != null && diff --git a/eslint-plugin/lib/rules/no-empty-image-src-attribute.js b/eslint-plugin/lib/rules/no-empty-image-src-attribute.js index 22480c6..45668c2 100644 --- a/eslint-plugin/lib/rules/no-empty-image-src-attribute.js +++ b/eslint-plugin/lib/rules/no-empty-image-src-attribute.js @@ -69,11 +69,13 @@ module.exports = { (attr) => attr.name.name === "src", ); if (srcValue?.value?.value === "") { + //to prevent Empty image context.report({ node: srcValue, messageId: "SpecifySrcAttribute", }); } else if (!srcValue) { + //to prevent context.report({ node, messageId: "SpecifySrcAttribute", diff --git a/test-project/package.json b/test-project/package.json index f49e1f6..48c3a9e 100644 --- a/test-project/package.json +++ b/test-project/package.json @@ -9,8 +9,8 @@ "license": "GPL-3.0", "author": "Green Code Initiative", "scripts": { - "lint": "eslint --ext .js,.ts,.vue src", - "lint:report": "eslint --ext .js,.ts,.vue src -f json -o eslint-report.json", + "lint": "eslint src/.", + "lint:report": "eslint src/. -f json -o eslint-report.json", "sonar": "sonar" }, "devDependencies": { diff --git a/test-project/src/vue/avoid-autoplay.vue b/test-project/src/vue/avoid-autoplay.vue index 6b2c9df..9343518 100644 --- a/test-project/src/vue/avoid-autoplay.vue +++ b/test-project/src/vue/avoid-autoplay.vue @@ -1,4 +1,5 @@ \ No newline at end of file diff --git a/test-project/src/vue/avoid-css-animations.vue b/test-project/src/vue/avoid-css-animations.vue index 9db1000..ff040c8 100644 --- a/test-project/src/vue/avoid-css-animations.vue +++ b/test-project/src/vue/avoid-css-animations.vue @@ -1,4 +1,4 @@ \ No newline at end of file diff --git a/test-project/src/vue/no-empty-image-src-attribute.vue b/test-project/src/vue/no-empty-image-src-attribute.vue index ac4a89d..e9ddbef 100644 --- a/test-project/src/vue/no-empty-image-src-attribute.vue +++ b/test-project/src/vue/no-empty-image-src-attribute.vue @@ -1,5 +1,5 @@ \ No newline at end of file diff --git a/test-project/src/vue/prefer-lighter-formats-for-image-files.vue b/test-project/src/vue/prefer-lighter-formats-for-image-files.vue index 920d4af..6b459bc 100644 --- a/test-project/src/vue/prefer-lighter-formats-for-image-files.vue +++ b/test-project/src/vue/prefer-lighter-formats-for-image-files.vue @@ -1,4 +1,4 @@ \ No newline at end of file diff --git a/test-project/src/vue/prefer-shorthand-css-notations.vue b/test-project/src/vue/prefer-shorthand-css-notations.vue index e384e7a..56821bc 100644 --- a/test-project/src/vue/prefer-shorthand-css-notations.vue +++ b/test-project/src/vue/prefer-shorthand-css-notations.vue @@ -1,4 +1,4 @@ \ No newline at end of file