-
-
Notifications
You must be signed in to change notification settings - Fork 1.2k
feat(eslint-plugin): add no-missing-exports rule (#2612) #3076
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: v3
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| 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" | ||
| }, | ||
| "scripts": { | ||
| "test": "eslint --rule 'pinia/no-missing-exports: error' tests/*.js && node tests/no-missing-exports.test.js" | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Test script has multiple issues. The test script attempts to run Additionally, 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"
}
🤖 Prompt for AI Agents |
||
| } | ||
| } | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| module.exports = { | ||
| rules: { | ||
| "pinia/no-missing-exports": "warn" | ||
| } | ||
| }; | ||
|
|
| 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"), | ||
| } | ||
| }; | ||
|
|
| 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
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Major: Missing validation for ObjectExpression and incomplete property handling. Line 23 checks Additionally, the property iteration doesn't handle:
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;
}
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| // 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; | ||||||||||||||||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Critical: Accessing Line 47 assumes 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
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||
| if (!returnedNames.has(name)) { | ||||||||||||||||||||||||||||||
| context.report({ | ||||||||||||||||||||||||||||||
| node: decl, | ||||||||||||||||||||||||||||||
| message: `Variable '${name}' is declared but not exported via return()`, | ||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||
|
Comment on lines
+44
to
+56
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Major: Rule only checks variable declarations, ignoring function declarations. The rule only checks 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 |
||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| 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()" }] | ||
| } | ||
| ] | ||
| }); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
ESLint 9 release date latest stable version
<web_search_result>
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.jsonRepository: 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
Sources:
</web_search_result>
Script executed:
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.0excludes 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