diff --git a/packages/eslint-plugin-pinia/package.json b/packages/eslint-plugin-pinia/package.json new file mode 100644 index 0000000000..b3c9e31a8d --- /dev/null +++ b/packages/eslint-plugin-pinia/package.json @@ -0,0 +1,13 @@ +{ + "name": "@pinia/eslint-plugin", + "version": "0.0.1", + "main": "src/index.js", + "type": "commonjs", + "peerDependencies": { + "eslint": "^8.0.0" + }, + "scripts": { + "test": "eslint --rule 'pinia/no-missing-exports: error' tests/*.js && node tests/no-missing-exports.test.js" + } + } + \ No newline at end of file diff --git a/packages/eslint-plugin-pinia/src/configs/recommended.js b/packages/eslint-plugin-pinia/src/configs/recommended.js new file mode 100644 index 0000000000..8b38c3e5d7 --- /dev/null +++ b/packages/eslint-plugin-pinia/src/configs/recommended.js @@ -0,0 +1,6 @@ +module.exports = { + rules: { + "pinia/no-missing-exports": "warn" + } + }; + \ No newline at end of file diff --git a/packages/eslint-plugin-pinia/src/index.js b/packages/eslint-plugin-pinia/src/index.js new file mode 100644 index 0000000000..6109066fb7 --- /dev/null +++ b/packages/eslint-plugin-pinia/src/index.js @@ -0,0 +1,10 @@ +module.exports = { + rules: { + "no-missing-exports": require("./rules/no-missing-exports"), + }, + + configs: { + recommended: require("./configs/recommended"), + } + }; + \ No newline at end of file diff --git a/packages/eslint-plugin-pinia/src/rules/no-missing-exports.js b/packages/eslint-plugin-pinia/src/rules/no-missing-exports.js new file mode 100644 index 0000000000..8fd8299521 --- /dev/null +++ b/packages/eslint-plugin-pinia/src/rules/no-missing-exports.js @@ -0,0 +1,61 @@ +/** + * Rule goal: + * Warn when variables declared inside a Pinia setup store + * are NOT returned in the final return statement. + */ + +module.exports = { + meta: { + type: "problem", + docs: { + description: + "Warn when variables declared inside a setup store are not exported via return()", + }, + schema: [], + }, + + create(context) { + return { + ReturnStatement(node) { + const returnedNames = new Set(); + + // read return object properties (return { a, b }) + if (node.argument && node.argument.properties) { + for (const prop of node.argument.properties) { + if (prop.key && prop.key.name) { + returnedNames.add(prop.key.name); + } + } + } + + // find variable declarations in the same function + const fn = context + .getAncestors() + .reverse() + .find( + (n) => + n.type === "FunctionDeclaration" || + n.type === "FunctionExpression" || + n.type === "ArrowFunctionExpression" + ); + + if (!fn || !fn.body || !fn.body.body) return; + + for (const stmt of fn.body.body) { + if (stmt.type === "VariableDeclaration") { + for (const decl of stmt.declarations) { + const name = decl.id.name; + if (!returnedNames.has(name)) { + context.report({ + node: decl, + message: `Variable '${name}' is declared but not exported via return()`, + }); + } + } + } + } + }, + }; + }, + }; + \ No newline at end of file diff --git a/packages/eslint-plugin-pinia/tests/no-missing-exports.test.js b/packages/eslint-plugin-pinia/tests/no-missing-exports.test.js new file mode 100644 index 0000000000..eb0c03bae1 --- /dev/null +++ b/packages/eslint-plugin-pinia/tests/no-missing-exports.test.js @@ -0,0 +1,29 @@ +const rule = require("../src/rules/no-missing-exports"); +const { RuleTester } = require("eslint"); + +const tester = new RuleTester({ + parserOptions: { ecmaVersion: 2020, sourceType: "module" } +}); + +tester.run("no-missing-exports", rule, { + valid: [ + ` + export const useX = defineStore('x', () => { + const a = 1; + return { a }; + }); + ` + ], + invalid: [ + { + code: ` + export const useX = defineStore('x', () => { + const a = 1; + const b = 2; + return { a }; + }); + `, + errors: [{ message: "Variable 'b' is declared but not exported via return()" }] + } + ] +});