Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions packages/eslint-plugin-pinia/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"name": "@pinia/eslint-plugin",
"version": "0.0.1",
"main": "src/index.js",
"type": "commonjs",
"peerDependencies": {
"eslint": "^8.0.0"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

ESLint 9 release date latest stable version


<web_search_result>

  • ESLint v9 initial release date: April 5, 2024. [1]
  • Latest stable v9 release (as of Nov 25, 2025): v9.39.1 — released November 3, 2025. [2]

Sources: [1] ESLint version support page; [2] ESLint release notes / release listing.
</web_search_result>


Script executed:

# Check the package.json file to see the full context
cat -n packages/eslint-plugin-pinia/package.json

Repository: vuejs/pinia


<shell_result>
Repository: vuejs/pinia
Script execution failed with unrecoverable error: [CAPABILITY:RUN_SHELL_SCRIPT] Failed to clone repository vuejs/pinia: Clone operation failed
</shell_result>


pinia eslint-plugin ESLint 9 compatibility support


<web_search_result>
Short answer: There’s no official ESLint‑9-ready release of eslint-plugin-pinia (the npm package v0.1.11 was published ~1 year ago and does not declare ESLint‑9 support). That means using ESLint 9 may cause peer‑dependency conflicts or require manual wiring (Flat config / FlatCompat) until the plugin adds explicit ESLint‑9/flat‑config support. [1][2]

What to do next

  • If you need ESLint 9 now: keep ESLint 9 but configure the plugin manually via eslint.config.js (or use ESLint’s FlatCompat/explicit plugin import) as documented in ESLint’s migration notes. [2]
  • If you want the simplest path: stay on ESLint 8 until eslint-plugin-pinia publishes explicit ESLint‑9 support or the plugin’s repo/packaged metadata is updated.
  • Confirm by checking the plugin’s repo/package.json or its npm page before upgrading; if you want, I can check the plugin’s repo/package.json for the latest peerDependencies now.

Sources:

  • eslint-plugin-pinia on npm (package info, last published). [1]
  • ESLint v9 / flat‑config migration and plugin compatibility guidance. [2]
    </web_search_result>

Script executed:

# Try using GitHub CLI to check recent commits/issues related to ESLint 9
gh repo view vuejs/pinia --json description,url
gh search issues "repo:vuejs/pinia ESLint 9" --limit 5

Repository: vuejs/pinia


<shell_result>
Repository: vuejs/pinia
Script execution failed with unrecoverable error: [CAPABILITY:RUN_SHELL_SCRIPT] Failed to clone repository vuejs/pinia: Clone operation failed
</shell_result>


Update peer dependency range to support ESLint 9, or document ESLint 9 incompatibility.

ESLint 9 was released on April 5, 2024, and is now widely adopted (v9.39.1 as of November 2025). However, eslint-plugin-pinia (v0.1.11) does not declare ESLint 9 support. The current peer dependency ^8.0.0 excludes ESLint 9 entirely, which will cause peer-dependency conflicts for users running ESLint 9. Either update the version range to support ESLint 9 if the plugin is compatible, or document the ESLint 8 requirement and provide migration guidance for ESLint 9 users.

🤖 Prompt for AI Agents
In packages/eslint-plugin-pinia/package.json around line 7, the peerDependency
for "eslint" is locked to "^8.0.0" which excludes ESLint 9; either expand the
range to declare compatibility (e.g., change the peerDependency to accept both
v8 and v9 such as "^8 || ^9" or ">=8 <10") after verifying the plugin builds and
tests cleanly under ESLint 9, or explicitly document in README/CHANGELOG that
only ESLint 8 is supported and add migration guidance; if you choose to expand
the range, run the test suite and a local lint run against an ESLint 9 snapshot,
then update package.json, bump the package version, and add a brief note to the
changelog/README stating ESLint 9 compatibility.

},
"scripts": {
"test": "eslint --rule 'pinia/no-missing-exports: error' tests/*.js && node tests/no-missing-exports.test.js"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Test script has multiple issues.

The test script attempts to run eslint --rule 'pinia/no-missing-exports: error' but the plugin isn't installed or loaded yet. ESLint won't recognize pinia/no-missing-exports without proper plugin registration.

Additionally, eslint is missing from devDependencies, which is required to run the test script.

Consider this approach:

     "peerDependencies": {
       "eslint": "^8.0.0"
     },
+    "devDependencies": {
+      "eslint": "^8.0.0"
+    },
     "scripts": {
-      "test": "eslint --rule 'pinia/no-missing-exports: error' tests/*.js && node tests/no-missing-exports.test.js"
+      "test": "node tests/no-missing-exports.test.js"
     }

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In packages/eslint-plugin-pinia/package.json around line 10, the test script
tries to run eslint with the rule pinia/no-missing-exports but eslint is not
listed in devDependencies and the plugin isn’t being registered for the run; add
eslint to devDependencies and update the test script to invoke the locally
installed eslint (e.g., via npx or node_modules/.bin/eslint) and explicitly load
the plugin when running (use --plugin pinia or ensure
resolve-plugins-relative-to points at this package) so the rule is recognized,
then keep the existing test runner invocation (node
tests/no-missing-exports.test.js).

}
}

6 changes: 6 additions & 0 deletions packages/eslint-plugin-pinia/src/configs/recommended.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
module.exports = {
rules: {
"pinia/no-missing-exports": "warn"
}
};

10 changes: 10 additions & 0 deletions packages/eslint-plugin-pinia/src/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
module.exports = {
rules: {
"no-missing-exports": require("./rules/no-missing-exports"),
},

configs: {
recommended: require("./configs/recommended"),
}
};

61 changes: 61 additions & 0 deletions packages/eslint-plugin-pinia/src/rules/no-missing-exports.js
Original file line number Diff line number Diff line change
@@ -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);
}
}
}
Comment on lines +23 to +29
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Major: Missing validation for ObjectExpression and incomplete property handling.

Line 23 checks node.argument.properties but doesn't verify that node.argument.type === 'ObjectExpression'. If someone returns a variable reference (return storeObject), this will fail silently or behave unexpectedly.

Additionally, the property iteration doesn't handle:

  • Spread properties: return { ...obj, a }
  • Computed property names: return { [key]: value }
  • Method definitions: return { action() {} }

Apply this diff to add ObjectExpression validation:

           const returnedNames = new Set();
   
           // read return object properties (return { a, b })
-          if (node.argument && node.argument.properties) {
+          if (node.argument && node.argument.type === "ObjectExpression" && node.argument.properties) {
             for (const prop of node.argument.properties) {
+              // Handle spread properties
+              if (prop.type === "SpreadElement") {
+                // For spreads, we can't determine exported names statically
+                // Consider all variables as potentially exported to avoid false positives
+                return;
+              }
               if (prop.key && prop.key.name) {
                 returnedNames.add(prop.key.name);
               }
             }
+          } else if (node.argument && node.argument.type === "Identifier") {
+            // Return of a variable reference - can't determine exports statically
+            return;
           }

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In packages/eslint-plugin-pinia/src/rules/no-missing-exports.js around lines 23
to 29, the code assumes node.argument has properties without verifying it's an
ObjectExpression and does not handle spread, computed, or method properties;
update the logic to first check that node.argument && node.argument.type ===
'ObjectExpression' before iterating, then for each property: skip any
SpreadElement, only process properties with type === 'Property', and for those
handle key types by adding identifier keys (prop.key.name) and literal keys
(String(prop.key.value)) to returnedNames while skipping computed keys
(prop.computed === true) and skipping methods (prop.method === true or prop.kind
!== 'init').


// 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;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Critical: Accessing decl.id.name will crash on destructuring patterns.

Line 47 assumes decl.id is an Identifier, but it could be an ArrayPattern (e.g., const [a, b] = arr) or ObjectPattern (e.g., const { x, y } = obj). This will cause a runtime error.

Apply this diff to add a guard:

             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()`,
-                  });
-                }
+                if (decl.id.type === "Identifier") {
+                  const name = decl.id.name;
+                  if (!returnedNames.has(name)) {
+                    context.report({
+                      node: decl,
+                      message: `Variable '${name}' is declared but not exported via return()`,
+                    });
+                  }
+                }
               }
             }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const name = decl.id.name;
if (stmt.type === "VariableDeclaration") {
for (const decl of stmt.declarations) {
if (decl.id.type === "Identifier") {
const name = decl.id.name;
if (!returnedNames.has(name)) {
context.report({
node: decl,
message: `Variable '${name}' is declared but not exported via return()`,
});
}
}
}
}
🤖 Prompt for AI Agents
In packages/eslint-plugin-pinia/src/rules/no-missing-exports.js around line 47,
the code uses decl.id.name assuming decl.id is an Identifier which will crash
for ArrayPattern or ObjectPattern; guard against non-Identifier nodes by
checking decl.id && decl.id.type === 'Identifier' before accessing .name, and
for other pattern types either skip processing those declarations or extract
their identifiers safely (e.g., traverse the pattern to collect names) depending
on intended behavior; update the logic to only read .name when the check passes
to avoid runtime errors.

if (!returnedNames.has(name)) {
context.report({
node: decl,
message: `Variable '${name}' is declared but not exported via return()`,
});
}
}
}
}
Comment on lines +44 to +56
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Major: Rule only checks variable declarations, ignoring function declarations.

The rule only checks VariableDeclaration statements but ignores FunctionDeclaration and FunctionExpression assigned to variables. Functions are also common exports in Pinia stores.

Example missing case:

defineStore('x', () => {
  function myAction() { } // Should be returned but isn't checked
  const state = ref(0);
  return { state }; // myAction is missing
});

Add a check for function declarations:

           for (const stmt of fn.body.body) {
             if (stmt.type === "VariableDeclaration") {
               for (const decl of stmt.declarations) {
                 if (decl.id.type === "Identifier") {
                   const name = decl.id.name;
                   if (!returnedNames.has(name)) {
                     context.report({
                       node: decl,
                       message: `Variable '${name}' is declared but not exported via return()`,
                     });
                   }
                 }
               }
             }
+            else if (stmt.type === "FunctionDeclaration" && stmt.id) {
+              const name = stmt.id.name;
+              if (!returnedNames.has(name)) {
+                context.report({
+                  node: stmt,
+                  message: `Function '${name}' is declared but not exported via return()`,
+                });
+              }
+            }
           }
🤖 Prompt for AI Agents
In packages/eslint-plugin-pinia/src/rules/no-missing-exports.js around lines
44–56, the loop only inspects VariableDeclaration nodes and ignores
FunctionDeclaration nodes (and function expressions assigned to variables should
be considered exports too); update the loop to also handle stmt.type ===
"FunctionDeclaration" by extracting stmt.id.name and reporting it if not in
returnedNames, and ensure variable declarations that have
function/arrow-function initializers are still checked (you can keep the
existing VariableDeclaration branch but treat declarators with init of type
FunctionExpression/ArrowFunctionExpression the same as other declarators), so
all function declarations/assignments inside the store are validated against
returnedNames.

},
};
},
};

29 changes: 29 additions & 0 deletions packages/eslint-plugin-pinia/tests/no-missing-exports.test.js
Original file line number Diff line number Diff line change
@@ -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()" }]
}
]
});