Created
November 15, 2025 14:10
-
-
Save sillvva/ecb57b6b4d21c16d20d121f693f36062 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| export default { | |
| meta: { | |
| type: "problem", | |
| docs: { | |
| description: | |
| "Enforce that exports in .remote.ts files use query(), command(), or form(), and only allow type exports", | |
| category: "Best Practices", | |
| recommended: true | |
| }, | |
| messages: { | |
| mustBeRemoteFunction: 'Export "{{name}}" must be a remote function (query(), command(), or form()).', | |
| onlyTypesAllowed: | |
| 'Only remote functions and type exports are allowed in .remote.ts files. Export "{{name}}" is not allowed.' | |
| }, | |
| schema: [] | |
| }, | |
| create(context) { | |
| const filename = context.getFilename(); | |
| // Only apply this rule to files ending in .remote.ts | |
| if (!filename.endsWith(".remote.ts")) { | |
| return {}; | |
| } | |
| const remoteFunctions = new Set(["query", "command", "form"]); | |
| function isRemoteFunction(node) { | |
| return node.type === "CallExpression" && node.callee.type === "Identifier" && remoteFunctions.has(node.callee.name); | |
| } | |
| function checkExportDeclaration(node) { | |
| // Allow type-only exports | |
| if (node.exportKind === "type") { | |
| return; | |
| } | |
| if (node.type === "ExportNamedDeclaration") { | |
| // Handle type-only named exports: export type { Foo } | |
| if (node.exportKind === "type") { | |
| return; | |
| } | |
| // Handle re-exports without declaration | |
| if (!node.declaration && node.specifiers) { | |
| for (const specifier of node.specifiers) { | |
| // Allow individual type specifiers: export { type Foo } | |
| if (specifier.exportKind === "type") { | |
| continue; | |
| } | |
| context.report({ | |
| node: specifier, | |
| messageId: "onlyTypesAllowed", | |
| data: { | |
| name: specifier.exported.name | |
| } | |
| }); | |
| } | |
| return; | |
| } | |
| if (node.declaration && node.declaration.type === "VariableDeclaration") { | |
| for (const declarator of node.declaration.declarations) { | |
| if (declarator.init) { | |
| if (!isRemoteFunction(declarator.init)) { | |
| context.report({ | |
| node: declarator, | |
| messageId: "mustBeRemoteFunction", | |
| data: { | |
| name: declarator.id.name | |
| } | |
| }); | |
| } | |
| } | |
| } | |
| } else if (node.declaration) { | |
| // Block any other declaration types (functions, classes, etc.) unless they're type declarations | |
| const name = node.declaration.id?.name || "unknown"; | |
| context.report({ | |
| node: node.declaration, | |
| messageId: "onlyTypesAllowed", | |
| data: { name } | |
| }); | |
| } | |
| } | |
| } | |
| return { | |
| ExportNamedDeclaration: checkExportDeclaration, | |
| // Block default exports entirely (they can't be types in the same way) | |
| ExportDefaultDeclaration(node) { | |
| context.report({ | |
| node: node.declaration || node, | |
| loc: node.loc, | |
| messageId: "onlyTypesAllowed", | |
| data: { name: "default" } | |
| }); | |
| }, | |
| // Block export * statements | |
| ExportAllDeclaration(node) { | |
| context.report({ | |
| node, | |
| loc: node.loc, | |
| messageId: "onlyTypesAllowed", | |
| data: { name: "*" } | |
| }); | |
| } | |
| }; | |
| } | |
| }; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment