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/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 diff --git a/eslint-plugin/docs/rules/avoid-autoplay.md b/eslint-plugin/docs/rules/avoid-autoplay.md index 24d9a32..e7f39c1 100644 --- a/eslint-plugin/docs/rules/avoid-autoplay.md +++ b/eslint-plugin/docs/rules/avoid-autoplay.md @@ -30,6 +30,13 @@ return ( This rule is build for [React](https://react.dev/) and JSX. +```jsx + + + + +``` + ## Resources ### Documentation diff --git a/eslint-plugin/docs/rules/avoid-css-animations.md b/eslint-plugin/docs/rules/avoid-css-animations.md index 77ff251..d5bab67 100644 --- a/eslint-plugin/docs/rules/avoid-css-animations.md +++ b/eslint-plugin/docs/rules/avoid-css-animations.md @@ -24,6 +24,15 @@ 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. + 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..83bb8e7 100644 --- a/eslint-plugin/docs/rules/no-empty-image-src-attribute.md +++ b/eslint-plugin/docs/rules/no-empty-image-src-attribute.md @@ -42,6 +42,14 @@ return ( This rule is build for [React](https://react.dev/) and JSX. +```jsx + +
+
+
+```
+
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..0f9fa51 100644
--- a/eslint-plugin/docs/rules/prefer-shorthand-css-notations.md
+++ b/eslint-plugin/docs/rules/prefer-shorthand-css-notations.md
@@ -64,6 +64,15 @@ 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.
+
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 350d319..765adc1 100644
--- a/eslint-plugin/lib/rules/avoid-autoplay.js
+++ b/eslint-plugin/lib/rules/avoid-autoplay.js
@@ -36,6 +36,59 @@ module.exports = {
schema: [],
},
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 +98,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/lib/rules/avoid-css-animations.js b/eslint-plugin/lib/rules/avoid-css-animations.js
index f181671..0de0900 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(
@@ -60,6 +87,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..45668c2 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") {
@@ -55,6 +83,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/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..4482f38 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"),
+ },
+ },
+});
+
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/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: [
+ "
",
+ "
",
+ "
",
+ 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/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..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,
@@ -16,4 +17,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..48c3a9e 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/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..9343518
--- /dev/null
+++ b/test-project/src/vue/avoid-autoplay.vue
@@ -0,0 +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
new file mode 100644
index 0000000..ff040c8
--- /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..e9ddbef
--- /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-shorthand-css-notations.vue b/test-project/src/vue/prefer-shorthand-css-notations.vue
new file mode 100644
index 0000000..56821bc
--- /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"